Invoke Javascript in WPF webbrowser without dispatcher - javascript

I have an application implementing the WPF WebBrowser control. It loads a page containing some JS functions that have to be called from my application, possibly from other threads. Preferrably, I would like to stick to the MVVM pattern and keep the code for parsing the function return in the model. Calling the InvokeScript method on the WebBrowser object should happen on the Dispatcher thread (and thus in the view), since it is a UI element.
The steps I currently take to get this job done is (roughly in pseudo):
- subscribe to the LoadCompleted event of the browser (view)
- set the browser source (model -> viewmodel -> view)
- catch the LoadCompleted event (view -> viewmodel -> model)
- some logic (model)
- invoke script (model -> viewmodel -> view)
- get script result (view -> viewmodel -> model)
- some logic (model)
This results in quite some back-and-forth communication between the model and the view (through the viewmodel). Since I am not that experienced with WPF (or MVVM in that matter), I am wondering whether there is a neater way of accomplishing this task (and by neater I mean: less calls and events between the model, viewmodel and view).

I know this is not exactly the answer to your question, but it might help you even more in the future. Take a look at the component called CefSharp. That's a c# wrapper around Chrome Embedded Framework. It is very MVVM friendly, it is open source, free and it's reasonably easy to develop for. I've recently moved to it from another Chrome wrapper (Awesomium) and very happy with it.
CefSharp allows you to call js functions from the page and it even supports async/await for those calls.

So main point of MVVM is to separate interface (which is platform specific: windows\android\ios\windows phone etc etc) from everything else, so that you could reuse logic (viewmodel\model) on different platforms. So it's clear you cannot call InvokeScript directly from viewmodel - but not because of dispatcher. Dispatcher you can abstract out (for example), since in some cases you need it in your view model. So usually when viewmodel needs to perform operation on view - events (or just delegates) are used:
public class MyViewModel
{
public Func<string, object> InvokeScript { get; set; }
public Func<string, Task<object>> InvokeScriptAsync { get; set; }
public async void Something() {
var result = await InvokeScriptAsync("my script");
// do something
}
}
And in your view:
public class MyView {
private void OnViewModelChanged(MyViewModel vm) {
vm.InvokeScript = text => Dispatcher.Invoke(() => browser.InvokeScript(text));
vm.InvokeScriptAsync = text => browser.InvokeScriptAsync(text); // for example
}
}

Related

Is there a way to use a reference to an existing java class from javascript in Vaadin?

i currently have the problem, that i want to use a reference of an existing java class in javascript to be able to execute methods from Javascript of the Java Class to use Shepherd (Javascript Tool for guiding users through the web app). Im using Vaadin for Web deployment with java but Shepherd only works through Javascript.
I dont want to execute the JS from the Class with the methods, moreover i want to execute it from an other class, so it looks like this:
User clicks on a Button
Button button = new Button(); button.addClickListener (c -> tourPageController.executeShepherd(UI.getCurrent().getInternals().getTitle());
Java Code to find active Page
public static void executeShepherd(String appTitle) {switch(appTitle) {case "example": UI.getCurrent().getPage().executeJs("window.startTour($0)", <here i want to reference the existing class with the methods);
execute the Shepherd Tour four the explicit page, the user is on (For that, i need to hand over
a reference of the class holding the methods to execute via javascript), its looks something like this in the header:
window.startTour = () => { ... }
Javascript is getting the parameter (the class)
From Javascript im going to use Shepherd, but there are references to Java Methods (these Methods are for the Vaadin Components, to open and close the respectively tabs i want to use in the tour). I use it like this:
beforeShowPromise: function () {
return new Promise(function(resolve) {
classParameter.$server.<method()>;
resolve();
});
},
How can i do this?
What you can do is to set id to the component in Java
component.setId("component-x");
This will mean that the DOM element will be have the id attribute set.
So if you have some bookkeeping of the components in your Java code, you can pass the id as a parameter in that #ClientCallable call, and then search for the component matching that id in your Java code.
If this is about only one view specific component, you do not need anything else than storing reference to the component in a class field (not even the id). But if you indeed have more than one Accordion in your view, the idea is like this.
public class MyView extends Div {
List<Accordion> accordions;
public MyView() {
Accordion accordion = new Accordion();
accordion.setId("accordion-1");
accordions.add(accordion);
...
}
#ClientCallable
public openAccordion(String id, int index) {
accordions.stream().filter(acc ->
acc.getId().equals(id)).findFirst().ifPresent(acc -> acc.open(null));
accordion.open(index);
}
}
For context, see: How can I change Vaadin Components in Java through Javascript

C# property name change effects ID in HTML

c# code:
public class Person{
public string Name { get; set; }
public age int { get; set; }
}
cshtml code which generates an id of #Person_Name
#Html.DropDownListFor(m => m.Person.Name)
javascript code:
$('#Person_Name').on("change", function () {
//Do something
}
If I change the Person class property name from Name to FullName. The next step is to modify the cshtml code to read as:
#Html.DropDownListFor(m => m.Person.FullName)
I understand you can manually go in and change the Jquery code, but if this change is made and the person making the change is unaware of the jquery, it is going to cause an error. Is there a way to prevent this from happening through some form of notification or logging? Rather than just remembering that the jquery needs to be changed.
You do have the option to use:
#Html.IdFor(x => x.Person.FullName)
Or
#Html.NameFor(x => x.Person.FullName)
That does rely on having small bits of script in pages, but for what id do this is mostly calling functions in .js resources which from my point of view I find better for reuse anyway.
You may still not get alerted to all issues unless you choose to compile your MVC views, you'll probably get a warning if you have the view open. How you set up your project to compile your MVC views will depend on the type of project but a quick Google should help with that one.

Where should my Javascript go for View Components?

I'm getting used to view components in MVC 6, and I asked a similar question a few years ago about partial views. If I build a view component encapsulating a common use-case that requires its own Javascript, where do I put that Javascript? I know that it is dangerous at best to have Javascript in partial views, but it would be a lot simpler to include it in the view component, rather than in the containing view or a separate file that has to be referenced by the containing view.
For example, say I have a view component that has two drop-downs. The selection in the first drop-down determines what items appear in the second drop-down. This is easily handled in Javascript, of course, but where do I put it?
From my experience with ASP.NET 5 View Components, I would say that the best thing to do with them is to keep them isolated and in one place, so they will be easily to manage in long-term projects.
In one of my ASP.NET projects, I've developed View Components structure like this one:
View, Backend code and Model are all in one place, so when you move around the folder, you are sure that you move whole component. Moreover, when you are modyfying them, you have quick access to all of their parts.
It will be convinient to put JavaScript which is highly coupled with a component also in such structure. You can do this by simply creating the file under the component's folder, and then writing a GULP TASK that will copy JS file to wwwroot. From that point, you will be able to link that JavaScript code on component's .cshtml using standard syntax:
<script src="~/Components/yourcomponent.js"></script>
To obtain such a structure in my project, I've extended Razor, to be able to search for my component's CSHTML's in proper place. To do this, I've added this code in Startup.cs:
public partial class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//non relevant code skipped
services.AddMvc().AddRazorOptions(ConfigureRazor);
}
public void ConfigureRazor(RazorViewEngineOptions razor)
{
razor.ViewLocationExpanders.Add(new ViewLocationExpander());
}
}
and the ViewLocationExpander class is:
public class ViewLocationExpander : IViewLocationExpander
{
protected static IEnumerable<string> ExtendedLocations = new[]
{
"/{0}.cshtml"
};
public void PopulateValues(ViewLocationExpanderContext context)
{
//nothing here
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
//extend current view locations
return viewLocations.Concat(ExtendedLocations);
}
}
Then, you invoke component like this (from any .cshtml view):
#await Component.InvokeAsync("NavigationComponent",new NavigationComponentModel())

Invoke ViewComponent using javascript

I have a web page with a couple of view components, when I click on these components I open a simple editor for it, see image below. If I edit the text and hit enter, I would like to rerender the view component and not the hole page. Is it possible to invoke a view component using javascript to get this behaviour?
With updates, you should now be able to do this with asp.net core. You can return a ViewComponent from an IActionResult, then just call it with jQuery:
[Route("text-editor")]
public IActionResult TextEditor()
{
return ViewComponent("TextEditorViewComponent");
}
Then this can be called from your View with something as simple as this:
function showTextEditor() {
// Find the div to put it in
var container = $("#text-editor-container");
$.get("text-editor", function (data) { container.html(data); });
}
If you need to pass parameters to the ViewComponent you can do so in the IActionResult (maybe they trickle through from the View to there, to the view component, etc).
This is not possible today but you could build a standard proxy for viewcomponents if you'd like though (similarly you could build a proxy to partial views). e.g. you a catchall attribute route on an action.

Setting Breeze Navigation Property in Knockout subscription

I am building a SPA (Single Page Application) using Breezejs and Knockoutjs.
I am running into an issue when trying to set a navigation property inside a knockout subscription. On the final line of the ko.subscription the console.log function shows me the entity, however, the WebPresences navigation property is null.
Not sure if the fact its in a ko.subscription really matters but I've been able to set the navigation prop just in a js function I call right before save, so I think it has some significance.
So here is my Entity Model
public partial class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public Nullable<int> WebId { get; set; }
public virtual WebPresence WebPresence { get; set; }
}
And here is my ko.subscription and relevant variables:
var vm = {
newEntity: ko.observable(datacontext.createBreezeEntity('Entity')),
newWebPresence: ko.observable(datacontext.newBreezeEntity('WebPresence')),
}
vm.newEntity().WebPresence.subscribe(
function (data) {
var self = this;
if (data === null)
self.target(vm.newWebPresence());
console.log(vm.newEntity());
}
);
And last but not least my datacontext
createBreezeEntity: function (entityName) {
return manager.createEntity(entityName);
},
newBreezeEntity: function (entityName) {
return manager.metadataStore.getEntityType(entityName).createEntity();
}
I do not understand what you're striving to accomplish.
One thing I'm pretty confident about ... is that your the datacontext.newBreezeEntity creates an object that is just hanging in thin air and isn't part of any navigation property.
Let's look at your datacontext.newBreezeEntity method:
return manager.metadataStore.getEntityType(entityName).createEntity();
This indeed does create a new entity of the type named entityName. But this is a 'proto-entity'. It does not belong to an EntityManager ... certainly not to the manager instance. It isn't related to any particular other entity (e.g., your mysteriously-named Entity entity). It's just a detached entity.
I'm betting you thought that it would belong to manager because you started the expression there. Well it doesn't. You lost the connection with manager the moment you asked for its metadataStore.
But there is so much else that makes no sense to me at all. I can't tell why you're subscribing to vm.newBreezeEntity nor why it's called "newBreezeEntity" nor what it's relationship is supposed to be to vm.newEntity nor what you think this is within the subscription function.
Perhaps you should step back and describe what you WANT this code to do.

Categories

Resources