How to ensure that data for JSONModel are loaded? - javascript

I maintain an SAP Fiori app that has been running for two years without any problems. After upgrading SAPUI5 from 1.56.x to 1.101.x, there are various errors that can be traced back to a place where I try to load data for a JSON model.
The error is:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'indexOf')
This is the code:
onAfterRendering: function (oEvent) {
var sButtonId = this.getModel("misc").getProperty("/ButtonId");
// ...
// Here the code breaks because the variable is undefined:
if (sButtonId.indexOf("Demand") > -1) {
//...
}
},
The error usually only appears when navigating to the app from the SAP Fiori launchpad. If you reload the app itself, 99.9% of the time no error occurs.
There is no call that describes or reads the variable beforehand. The error appears directly at the first use.
I was trying to debug the UI5 framework a bit and came across internal method JSONModel.prototype._getObject:
When I call the app directly, this method returns the appropriate data and the app works. When I call the app from the launchpad, the method returns null. The model has not been loaded yet (oNode === undefined).
I then looked at the network and found that when I call the app directly, the JSON file/model has already been loaded before the above function is called, whereas when I load the app from the launchpad, the JSON file has been requested but no content has been served yet.
So it seems to be an asynchronous problem. I cannot use async/await, because UI5 officially only supports ECMAScript 5 (ES5) and we cannot deploy otherwise.
The model is declared and preloaded via the app descriptor (manifest.json).

Same as a/61879725 or a/63892279, you'll need to refactor the code by using dataLoaded1 from the JSONModel to ensure that the data have been loaded:
// Given: JSONModel and data source declared in the application descriptor (manifest.json)
thatJSONModel.dataLoaded().then(function() { // Ensuring data availability instead of assuming it.
var sButtonId = thatJSONModel.getProperty("/ButtonId"); // sButtonId is now defined.
// ...
}.bind(this));
Btw. I'd strongly discourage applications from relying on onBeforeRendering/onAfterRendering since it's unpredictable for them when and how many times the framework or a control initiates rendering.2
1 API Reference: sap.ui.model.json.JSONModel#dataLoaded
2 For example, the recent commit 8e24738 reduces the number of render calls in applications.

Related

Accessing CefSharp JS Object from remote URL

I have a web app running locally at http://localhost:3000/. I started with the CefSharp.MinimalExample.WinForms project, pointed it to this URL and the app starts up successfully. My next step was to test out the C# <-> JS bridge to see how those calls work.
I followed the How do you expose a .NET class to Javascript? documentation and have the .NET side set up to have a class that can be called by JavaScript. The next step is to call CefSharp.BindObjectAsync in JavaScript to initiate the binding, but on my site CefSharp is undefined on the JavaScript side. The error in chrome I receive Uncaught (in promise) ReferenceError: CefSharp is not defined. My understanding of CefSharp is that it will bind the appropriate CefSharp methods to the window object so that it can be accessed from the JS side. Will this not work if I'm accessing a remote site that isn't included in the actual .NET project? It seems like I'm missing something stupidly simple, but after a few passes through the docs I am still stuck.
I had copied pieces of my Program.cs from this file. I misunderstood the purpose of BrowserSubprocessPath and left it in there. After removing that setting I can get to the CefSharp object in JS. My guess is that this setting was initializing the CefSharp object in the wrong place.
Doc on BrowserSubprocessPath:
// The path to a separate executable that will be launched for sub-processes. By
// default the browser process executable is used. See the comments on Cef.ExecuteProcess()
// for details. Also configurable using the "browser-subprocess-path" command-line
// switch. Default is CefSharp.BrowserSubprocess.exe

Get access to Angular service instance from JavaScript code

What I'm trying to do is have some testing assertions based on the data in the Angular service, i.e. we're trying to create E2E tests and the tool we're using allows us to execute arbitrary JavaScript code for assertions, so for that I need to know if it's possible to get access to the Angular service instance.
How can I get access to an Angular service instance from plain JS code?
That is, if my Angular app is deployed, and I open the app in the browser, then open Chrome DevTools, can I get access to the service instance of the my Angular service that was provided to all components?
I know it's possible to get access to your component by through ng.probe($0) etc. but not sure about services.
From what I have searched so far, it seems like we have to do use the Injector class and then use it's .get() method to get access to one of the Angular service instances but I'm not sure how would I get access to the Injector class/instance itself?
Here's what I tried: ng.probe($0) ($0 being the <app-root> of my app) and then I see that the return value has an .injector property, I tried to call ng.probe($0).injector.get('MyServiceName') and got an error for: Uncaught Error: No provider for MyServiceName!.
(Even though I'm trying ng.probe above, I would love to know how to get access to the injector without ng.probe because during execution of the automated testing i.e. prod mode, I don't think I'll be able to do ng.probe($0))
So I'm not sure if I'm trying to access it the right way? Any ideas?
I'm using Angular 4.
This works for me in Angular 7 using ng.probe():
window.ng.probe(window.getAllAngularRootElements()[0])
.injector.view.root.ngModule._providers
.find(p => p && p.constructor && p.constructor.name === 'MyServiceName');
And I guess it is not possible to do it another way without ng.probe()

Writing to File Sometimes Failing in Windows 10 Universal Javascript App

I've developed a Universal App for Windows. It is deployed in my business, not through the App Store. It's perfectly functional 99.9% of the time.
But there's this one problem that's happened a couple of times.
I store persistent data in JSON format in my application local data folder.
To store my data, I use:
var storeData = function () {
return WinJS.Application.local.writeText('data', JSON.stringify(dataObject));
}
And to load my data, I use:
var loadData = function () {
return WinJS.Application.local.readText('data').then(function (text) {
dataObject = text ? JSON.parse(text) : {};
})
}
There's been a couple of cases where, during the loadData method, my app crashes. When I researched why it crashed, it turns out that there's an extra file in my local appdata folder. Obviously the file that's supposed to be there is called 'data' -- this extra file is called something like 'data-84DB.TMP' or something like that -- obviously a temporary file created as part of the file io API. In these cases where my app is crashing, this file has the information in it that you'd normally expect to see in the 'data' file, and the 'data' file is no longer in text format and when I open it in SublimeText it says '0000'.
So when my loadData function runs, it crashes.
Now, this is not a situation in which I want it to fail silently. I'd rather it crash than, say, just do a try-catch in loadData and make my dataObject empty or something in the cases where the data didn't save right. I'd rather it crash, so that then at least I can go and find the .TMP file and restore the information that didn't properly save.
Is this problem normal? Am I not following best practices for reading and writing persistent data? How can I fix this issue? Do you guys know what causes it? Might it be that the app unexpectedly shuts down in the middle of a file-writing operation?
I think the best practice should be to do reads and saves asynchronously. I guess WinJS.Application.local is a wrapper for WinRT API, but it works synchronously. I would suggest to use Windows.Storage.FileIO.writeTextAsync and Windows.Storage.FileIO.readTextAsync.
Also I found out that JSON.parse crashes (throws an error) when the string to parse is empty (""). In your case I see that it can be a problem as well since you're testing only if it's null or undefined, not if it's empty.

Reload module at runtime

I am considering to port a highly demanded(lots of traffic) sockets-based architecture from .NET to Node.JS using Socket.IO.
My current system is developed in .NET and use some scripting languages, loaded at runtime, so I can do hot-fixes if needed by issuing a reload command to the server, without having to restart the different servers/dispatcher processes.
I originally built it this way so, like I said, I could do hot fixes if needed and also keep the system available with transparent fixes.
I am new to Node.JS but this is what I want to accomplish:
Load javascript files on demand at runtime, store them in variables somewhere and call the script functions.
What would be the best solution? How to call a specific function from a javascript file loaded at runtime as a string? Can i load a javascript file, store it in a variable and call functions in a normal way just like a require?
Thanks!
If I understood your question correctly. You can check the vm module out.
Or if you want to be able to reload required files, you must clear the cache and reload the file, something this package can do. Check the code, you'll get the idea.
Modules are cached after the first time they are loaded. This means
(among other things) that every call to require('foo') will get
exactly the same object returned, if it would resolve to the same
file.
Multiple calls to require('foo') may not cause the module code to be
executed multiple times. This is an important feature. With it,
"partially done" objects can be returned, thus allowing transitive
dependencies to be loaded even when they would cause cycles.
More information can be found here.
Delete the cached module:
delete require.cache[require.resolve('./mymodule.js')]
Require it again. (maybe a require inside a function you can call)
update
Someone I know is implementing a similar approach. You can find the code here.
You can have a look at my module-invalidate module that allows you to invalidate a required module. The module will then be automatically reloaded on further access.
Example:
module ./myModule.js
module.invalidable = true;
var count = 0;
exports.count = function() {
return count++;
}
main module ./index.js
require('module-invalidate');
var myModule = require('./myModule.js');
console.log( myModule.count() ); // 0
console.log( myModule.count() ); // 1
mySystem.on('hot-reload-requested', function() {
module.invalidateByPath('./myModule.js'); // or module.constructor.invalidateByExports(myModule);
console.log( myModule.count() ); // 0
console.log( myModule.count() ); // 1
});

Silverlight 4 MVVM: Call Javascript function from viewmodel

we have developed an Intranet Management Application with Silverlight 4. We have been asked to add the functionality to call a remote desktop tool which is installed on clients using the Intranet SL App. In an earlier version of the tool written in ASP.NET we just added a Javascript function to the aspx page like this:
function RunShellCommand()
{
var launcher = new ActiveXObject("WScript.Shell");
launcher.Run("mstsc.exe");
}
and called it from ASP.NET.
Now it's clear that SL4 is running in a sandbox and that I cant use the AutomationFactory to create a WScript.Shell object (out of browser mode is not an option).
I thought I could circle around the problem by, again, adding the RunShellCommand javascript method in the aspx page where the SL4 control is hosted and call it via
HtmlPage.RegisterScriptableObject("Page", this);
HtmlPage.Window.Invoke("RunShellCommand", "dummydata");
from my ViewModel. When I run the Application the debugger just skips the RegisterScriptableObject method and quits. Nothing happens.
My question is if am doing something wrong or if this just wont work this way.
Is it possible that I cant do a RegisterScriptableObject from a viewmodel?
EDIT: When I explicitly put a try, catch block around the two methods I get an ArgumentException from the first method stating that the current instance has no scriptable members. When I delete the first method and only run the Invoke, I get a browser error stating that the automation server cant create the object. So is there really no way (except OOB mode) to do this?
Yes, the explanation is correct: you should add at least one method with the ScriptableMember attribute in order that you can use the RegisterScriptableObjectmethod. But it is used only for calling C#-methods from JavaScript.
As far as I see, you want to do the opposite: to call JavaScript code from the Silverlight application. Then you need only one line:
HtmlPage.Window.Invoke("RunShellCommand");
The error automation server cant create the object has nothing to do with Silverlight. I'm sure that if you call the JS function directly - the error will remain.
According to the internet, the reason might be not installed Microsoft Windows Script. Or it is because of security restrictions of the browser.

Categories