New to GWT but I love it so far. I do have a problem that's easy to reproduce. It is a big problem for me because I want to create a GWT Module for PubNub - a utility we use internally.
I created a demo project to test out encapsulation and I have found an interesting problem with ScriptInjector/Pubnub.
At first I followed the PubNub instructions. NOTE: I have included my keys for my test account. Feel free to use them.
Following the instructions I put these two items in the html file in the GWT project (with my keys specified):
<div pub-key="pub-b8b75fbd-c4cf-4583-bab9-424af7a7f755" sub-key="sub-5e843c94-1193-11e2-bba9-b116c93082cf" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
<script src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
When I do this, I can use JSNI to access pubnub. It all works great.
What doesn't work is if I delete the tag here and inject the script instead with the following code. I know the script injects because I can see the success message and I can see the script in Developer tools in Chrome.
ScriptInjector.fromUrl("http://cdn.pubnub.com/pubnub-3.1.js").setCallback(
new Callback<Void, Exception>() {
public void onFailure(Exception reason) {
Window.alert("Script load failed.");
}
public void onSuccess(Void result) {
Window.alert("Script load success.");
}
}).inject();
I feel this must be somehow related to accessing the DOM with the delayed script, or because the script is not a part of the DOM. It's trying to access the setup div and it's not able to...(just my guess)
Any thoughts? I need to move the items out of the html file because I need to modularize this project for use in other larger projects. Any help would be appreciated.
(PS, I have tried to create html widgets as well, and add them in EntryPoint. This also adds the tag to the page based on my browsing in Developer Tools in Chrome, but it fails to work just as ScriptInjector fails.)
EDIT: Here is as simple a project as I can make to demo the problem:
html file:
above the closing body tag:
<div pub-key="pub-b8b75fbd-c4cf-4583-bab9-424af7a7f755" sub-key="sub-5e843c94-1193-11e2-bba9-b116c93082cf" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
<script src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
<script src="pubnubWrapper.js"></script>
pubnubWrapper.js (basically what's at the pubnub website):
function subToPubNub(){
PUBNUB.subscribe({
channel : "hello_world", // CONNECT TO THIS CHANNEL.
restore : false, // STAY CONNECTED, EVEN WHEN BROWSER IS CLOSED
// OR WHEN PAGE CHANGES.
callback : function(message) { // RECEIVED A MESSAGE.
alert(message);
},
disconnect : function() { // LOST CONNECTION.
alert(
"Connection Lost." +
"Will auto-reconnect when Online."
)
},
reconnect : function() { // CONNECTION RESTORED.
alert("And we're Back!")
},
connect : function() { // CONNECTION ESTABLISHED.
PUBNUB.publish({ // SEND A MESSAGE.
channel : "hello_world",
message : "Hi from PubNub."
})
}
})
}
ScriptTest.java:
public class ScriptTest implements EntryPoint {
public void onModuleLoad() {
//add a button to sub to pubnub
Button subButton = new Button("Sub");
//add the event handler for button
subButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
//sub to pubnub
callSub();
}
});
RootPanel.get().add(subButton);
}
public native void callSub() /*-{
//call my wrapper to pubnub
$wnd.subToPubNub();
}-*/;
}
This all works as is.
If you remove the pubnub script from the html file and add it with script injector, it fails. If you add the script to the gwt.xml file, it fails.
Any Ideas?
Can you post your JSNI code?
I bet you are accessing PubNub via the $wnd variable right?
If you put the script tags in your module's xml file the pubnub code will be loaded in the host's page window (you can check with Chrome DeveloperTools).
ScriptInjector by default won't load it there but in the GWT namespace.
So in order to load it into the host's window namespace you have to pass the TOP_WINDOW to the ScriptInjector
ScriptInjector.fromUrl("http://cdn.pubnub.com/pubnub-3.1.js").setCallback(
new Callback<Void, Exception>() {
public void onFailure(Exception reason) {
Window.alert("Script load failed.");
}
public void onSuccess(Void result) {
Window.alert("Script load success.");
}
}).setWindow(ScriptInjector.TOP_WINDOW).inject();
Related
How to make an Unity WebGL project to read a kind of configuration file (any format), which is editable after the "Build" from Unity workspace.
Below is the sample of Build directory, which contains the packaged files
The use case is to have the backend API using by this WebGL project to be configurable at the hosting server, so that when the player/user browse it, it knows where to connect to the backend API.
The closest part I could explore currently is to implement the custom Javascript browser scripting. Any advice or any existing API could be used from Unity?
An update for the chosen solution for this question. The Javascript browser scripting method was used.
Total of 3 files to be created:
WebConfigurationManager.cs
Place it in the asset folder. This file is the main entry for the C# code, it decides where to get the web configuration, either via the default value from another C# class (while using the unity editor), or using the browser scripting method to retrieve (while browsing the distribution build via browser).
WebConfigurationManager.jslib
Place it the same folder as WebConfigurationManager.cs. This file is the javascript code, to be loaded by browser.
web-config.json
Your JSON configuration. The web configuration file could be hosted anywhere, example below placed under the root of the distribution build folder, you'll have to know where to load the file, for example https://<website>/web-config.json.
// WebConfigurationManager.cs
using System;
using UnityEngine;
using System.Runtime.InteropServices;
using AOT;
public class ConfigurationManager : MonoBehaviour
{
#if UNITY_WEBGL && !UNITY_EDITOR
// Load the web-config.json from the browser, and result will be passed via EnvironmentConfigurationCallback
public delegate void EnvironmentConfigurationCallback(System.IntPtr ptr);
[DllImport("__Internal")]
private static extern void GetEnvironmentConfiguration(EnvironmentConfigurationCallback callback);
void Start()
{
GetEnvironmentConfiguration(Callback);
}
[MonoPInvokeCallback(typeof(EnvironmentConfigurationCallback))]
public static void Callback(System.IntPtr ptr)
{
string value = Marshal.PtrToStringAuto(ptr);
try
{
var webConfig = JsonUtility.FromJson<MainConfig>(value);
// webConfig contains the value loaded from web-config.json. MainConfig is the data model class of your configuration.
}
catch (Exception e)
{
Debug.LogError($"Failed to read configuration. {e.Message}");
}
}
#else
void Start()
{
GetEnvironmentConfiguration();
}
private void GetEnvironmentConfiguration()
{
// do nothing on unity editor other than triggering the initialized event
// mock the configuration for the use of Unity editor
var testConfig = JsonUtility.FromJson<MainConfig>("{\n" +
" \"apiEndpoint\": \"ws://1.1.1.1:30080/events\",\n" +
" \"updateInterval\": 5\n" +
"}");
Debug.Log(testConfig.apiEndpoint);
Debug.Log(testConfig.updateInterval);
}
#endif
}
// WebConfigurationManager.jslib
mergeInto(LibraryManager.library, {
GetEnvironmentConfiguration: function (obj) {
function getPtrFromString(str) {
var buffer = _malloc(lengthBytesUTF8(str) + 1);
writeStringToMemory(str, buffer);
return buffer;
}
var request = new XMLHttpRequest();
// load the web-config.json via web request
request.open("GET", "./web-config.json", true);
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
var buffer = getPtrFromString(request.responseText);
Runtime.dynCall('vi', obj, [buffer]);
}
};
request.send();
}
});
I'm trying to do the following:
I want to add a specific handler for some links, denoted by a class.
$("a.link_list").live("click", new ListLinkHandler());
I need .live() instead of .bind() because new such links will be generated. (I know jQuery's .live() is deprecated in favor of .on(), but gwt-query doesn't have a .on() yet.)
I defined the handler like this (just as the gwtquery example does):
public class ListLinkHandler extends Function {
#Override
public boolean f(Event e) { [...] }
}
However, the handler method is never called when I click the links.
I can see the event listener in Chrome Dev Tools: http://screencloud.net/v/bV5V. I think it's on the body because it's a .live().
I tried using .bind() and it worked fine. The body event listener changed in a a.link_list and the handler does what it's supposed to do, but (as documented, I didn't test) not for newly created links.
I filed a bug for the .live() method, but maybe I'm doing something wrong.
Also, I have no idea how to do it without gwtquery, GWT doesn't seem to have a method for selecting elements by class, neither to continually add the listener to new elements.
It seems you are doing something wrong, but I need more code to be sure. Could you send the complete onModuleLoad code which demonstrates this wrong behavior?
I have written a quick example using live, and it works either when adding new gwt widgets or dom elements with gquery, in both Chrome and FF
public void onModuleLoad() {
$("a.link_list").live("click", new ListLinkHandler());
// Add a new link via gquery
$("<a class='link_list' href=javascript:alert('href') onClick=alert('onClick')>Click </a>").appendTo(document);
// Add a new link via gwt widgets
Anchor a = new Anchor("click");
a.setStyleName("link_list");
a.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("clickHandler");
}
});
RootPanel.get().add(a);
}
public class ListLinkHandler extends Function {
#Override
public boolean f(Event e) {
Window.alert("live");
return true;
}
}
Currently I am facing a problem which I am unable to solve after a lot of work and search, I asked a similar question before but didn't got any reply maybe because I didn't asked it correctly so I deleted that question.
Ok I am downloading emails using MailBEE.net Objects library and it is working fine except that if downloading method is called again while a previous call is still in downloading phase then two copies of messages start to download which is wrong.
on an ASP.net page I am calling an ASHX handler that downloads the emails
public class sync : IHttpHandler {
public void ProcessRequest(HttpContext context)
{
ApplicationData.layer Functions = new layer();
context.Response.ContentType = "text/text";
int messageCount = Convert.ToInt16(context.Request.QueryString["messageCount"]);
if (Functions.SyncMail("email", "user", "password", "pop.gmail.com", messageCount) == "Successful")
{
context.Response.Write("New Messages Downloaded.");
}
//context.Response.Write("Hello World");
}
}
I am calling the above ASHX handler using Jquery from another (ASPX) page
<script type="text/javascript">
$(document).ready(function() {
asyncLoad();
});
function asyncLoad() {
document.getElementById("CustomerDetails").innerHTML = "<img alt='' src='Images/ajax-loader.gif' />" + " <span>Downloading New Messages...</span>";
$('#CustomerDetails').load("sync.ashx?messageCount=" + "10");
callAgain();
}
function callAgain() {
setInterval(asyncLoad, 20000);
}
</script>
The purpose is to keep on calling sync.ashx after some time (The 20000 delay) to check new messages, the problem is if one call of sync.ashx is busy in downloading messages and a new call is made during this time, it starts to download identical copies of messages since it does not find the ids of emails in database which previous call is going to make.
I need some sort of mutually exclusive access, that if once call is busy in downloading messages then another call should not be made.
Something like
if(IsAlreadyDownloading == False)
{
Functions.SyncMail(params)
}
Where IsAlreadyDownloading is a global flag or mutex that should be set True once one call start downloading and be set false once it finish downloading or some exception has occurred indicating that another call can be made safely.
Since it is an ASP.net application we don't know when user will navigate away from the page which start download call and when it will navigate back to that page so another call to download handler should be made or not.
I don't know if I explained it properly or not but I hope someone will understand. Thank you.
Finally after much hair pulling and head scratching I managed to solve this problem, I don't know if it's the best solution but at least it is working properly now, if you know a better solution please share it here, thanks.
Here is my solution.
I used global.asax to declare application level global variable named as follows
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
Application["IsAlreadyDownloading"] = false;
}
The purpose is to mimic mutex like behavior and for that I left sync.ashx file and made another an aspx file, the reason for shifting from ashx file is access application level variable IsAlreadyDownloading which is unavailable in a generic handler. The code of aspx file is as follows
<%# Page Language="C#" %>
<%# Import Namespace="ApplicationData" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
int messageCount = Convert.ToInt16(Request.QueryString["messageCount"]);
ApplicationData.layer Functions = new layer();
try
{
if (!Convert.ToBoolean(Application["IsAlreadyDownloading"]))
{
Application["IsAlreadyDownloading"] = true;
if (Functions.SyncMail("email", "user", "password", "pop.gmail.com", messageCount) == "Successful")
{
Response.Write("New Messages Downloaded.");
Application["IsAlreadyDownloading"] = false;
}
else
{
Response.Write("<p>Unable to download some messages</p>");
Application["IsAlreadyDownloading"] = false;
}
}
}
finally
{
Application["IsAlreadyDownloading"] = false;
}
Response.End();
}
</script>
No matter how many times we make call to this page the method will not work as it will find IsAlreadyDownloading = true if another call of method is busy in work, as soon as it will finish it will again set IsAlreadyDownloading = false so a new call can acquire it.
Hope this help someone in similar problem.
server
I'm using GWT 2.4 with JUnit 4.8.1. When writing my class that extends GWTTestCase, I want to simulate clicking on a button on the page. Currently, in my onModuleLoad method, this button is only a local field ...
public void onModuleLoad() {
final Button submitButton = Button.wrap(Document.get().getElementById(SUBMIT_BUTTON_ID));
...
// Add a handler to send the name to the server
GetHtmlHandler handler = new GetHtmlHandler();
submitButton.addClickHandler(handler);
How do I simulate clicking on this button from the GWTTestCase? Do I have to expose this button as a public member accessor is there a more elegant way to access it? Here is what I have in my test case so far ...
public class GetHtmlTest extends GWTTestCase {
// Entry point class of the GWT application being tested.
private Productplus_gwt productPlusModule;
#Override
public String getModuleName() {
return "com.myco.clearing.productplus.Productplus_gwt";
}
#Before
public void prepareTests() {
productPlusModule = new Productplus_gwt();
productPlusModule.onModuleLoad();
} // setUp
#Test
public void testSuccessEvent() {
// TODO: Simulate clicking on button
} // testSuccessEvent
}
Thanks, - Dave
It can be as easy as buttonElement.click() (or ButtonElement.as(buttonWidget.getElement()).click(), or ButtonElement.as(Document.get().getElementById(SUBMIT_BUTTON_ID)).click())
But remember that a GWTTestCase doesn't run in your own HTML host page, but an empty one, so you'll first have to insert your button within the page before simulating your module's load.
gwt-test-utils seems to be the perfect framework to answer your need. Instead of inheriting from GWTTestCase, extend the gwt-test-utils GwtTest class and implement your click test with the Browser class, like shown in the getting starting guide :
#Test
public void checkClickOnSendMoreThan4chars() {
// Arrange
Browser.fillText(app.nameField, "World");
// Act
Browser.click(app.sendButton);
// Assert
assertTrue(app.dialogBox.isShowing());
assertEquals("", app.errorLabel.getText());
assertEquals("Hello, World!", app.serverResponseLabel.getHTML());
assertEquals("Remote Procedure Call", app.dialogBox.getText());
}
If you want to keep your button private, you'd be able to retrieve it by introspection. But my advice is to make you view's widgets package protected and to write your unit test in the same package so it could access them. It's more convinent and refactoring-friendly.
gwt-test-utils provide introspection convinence. For example, to retrieve the "dialogBox" field which could have been private, you could have do this :
DialogBox dialogBox = GwtReflectionUtils.getPrivateFieldValue(app, "dialogBox");
But note that using GwtReflectionUtils is not mandatory. gwt-test-utils allows you to use ANY java classes in GWT client side tests, without restriction :)
You can do it like this:
YourComposite view = new YourComposite();
RootPanel.get().add(view);
view.getSubmitButton.getElement().<ButtonElement>cast().click();
Im curios about this. I have for example this code :
button_article.addClickListener(new ClickListener(){
public void onClick(Widget w) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable reason) {
// somethings
}
public void onSuccess() {
content.clear();
content.designArticles();
}
});
}
});
public final void designArticles() {
this.add(new ProfileArticles(this.rpcService, this));
}
I see that until i click on button_article, the elements on ProfileArticles() (that is a FlowPanel) arent loaded when i start the application. So, how can GWT know that element on that class shouldnt loaded when the application start? It check each methods under GWT.runAsync() and their correspondents Class?
I also see that when i leave that "context" they arent released (in fact, if i change context and i return there, when i click again on that method it doesnt call the server. So it use the previous loaded code). Is it right? :)
Cheers
The GWT compiler analyzes the flow of your program to figure out what chunks it can load later. If you want to visually understand what it's done, check out http://code.google.com/webtoolkit/doc/latest/DevGuideCompileReport.html .
Once code is loaded, most of it can be cached, so even if the user navigates off the page and then back to yours, the code will not need to be reloaded.