I've got a language problem with my SAPUI5 controls.
If I execute e.g.:
sap.ui.getCore().getConfiguration().setLanguage("de");
My i18n files are loaded correctly and all labels are translated to German. But the controls are still in English.
The only way to get German controls is with the URL parameter:
sap-ui-language=DE
But I can't use a parameter in my case. Any idea?
Please note that sap.ui.getCore().setLanguage() explicitly states
The framework does not guarantee that already created, language dependent objects will be updated by this call. It therefore remains best practice for applications to switch the language early, e.g. before any language dependent objects are created. Applications that need to support more dynamic changes of the language should listen to the localizationChanged event and adapt all language dependent objects that they use (e.g. by rebuilding their UI).
Besides that, I fully support Nabi's answer (but I'm not allowed to vote it up).
I just would like to add that controls (like FilterBar) better should use the hook approach:
FilterBar.prototype.onlocalizationChanged = function(oEvent) {
// .. same bundle update code as in Nabi's proposal
}
Using the hook in controls avoids the need for adding attach + detach calls in init / exit and keeps the event registry small.
I can easily confirm the behavior you described by testing the Explored App Example. There, just open the console and hit sap.ui.getCore().getConfiguration().setLanguage("de");
I also checked the implementation of the FacetFilter and I would call this a bug in the Control implementation. It comes from how the texts are loaded inside the control. Just in case you are interested:
The message bundles all contain the correct translations for FACETFILTER_INFOBAR_NO_FILTERS (for en the translation comes from the "default" bundle):
messagebundle.properties
messagebundle_de.properties
The FacetFilter has a hidden aggregation called SummaryBar. The SummaryBar contains the text you see. Of course, this text comes from a bundle.
However, the bundle is initialized exactly once in init() by calling sap.ui.getCore().getLibraryResourceBundle("sap.m");. Here the API docs say:
If only one argument is given, it is assumed to be the libraryName.
The locale then falls back to the current session locale.
This means the bundle is cached and therefor changes to the localization (e.g. language) do not trigger the bundle to load a new translation file. Thus, we will always see the initial language no matter what we try (even rerendering() does not help).
A solution would be to fix the control by adding the following code right after the the bundle gets loaded inside the init:
sap.ui.getCore().attachLocalizationChanged(function(oEvent){
var oChanges = oEvent.getParameter("changes");
if (oChanges && oChanges.language){
this._bundle = sap.ui.getCore().getLibraryResourceBundle("sap.m", oChanges.language);
this.rerender();
}
}.bind(this));
You can try this out in the explored app linked above, it worked for me just fine...
I just opened an issue on github.
Related
I have a small application with two pages: the login page and the main page. My problem is that when I use just one main JavaScript file for both pages (like it's recommanded) I get a lot of ReferenceError because some variables on a page are not defined on the other one...
e.g:
Line of code for the login page
copyrightYear.textContent = new Date().getFullYear();
Main page complains
Uncaught TypeError: Cannot set property 'textContent' of null
How can I fix that? Don't tell me I have to say if(copyrightYear) { do stuff } everytime, it's just a nightmare if I have to do that for every variable
Two answers for you:
The recommendation isn't a dictate
My problem is that when I use just one main JavaScript file for both pages (like it's recommanded)
That's a very general recommendation. It doesn't apply to every situation. There's no point in loading code in a page that won't use that code.
If you have code that you use in both pages, and also page-specific code, it's absolutely fine to have a file that both pages share and also page-specific files:
<script src="main.js"></script>
<script src="page1.js"></script>
If you're really worried about the extra HTTP request (much less of an issue these days than it used to be), use modules and a bundler like Webpack or Rollup that will create a bundle combining the main module with page 1's module for page 1 and another bundle combining the main module with page 2's module for page 2.
But even then, the extra HTTP request may be better for your users, if you expect them to go from page1 to page2 in a reasonable timeframe. The reason is that if you have main.js and page1.js/page2.js and you allow caching of them, when the user goes to page1 they get main.js and page1.js, and then when they go to page2 main.js comes out of their local cache and they only have to load page2.js. In contrast, if you had a single bundle file for each page, they'd have to re-download all of that common code when going from page1 to page2 (or vice versa). But if you expect a visitor to visit either page1 or page2 but not the other one, you save the HTTP request by using page-specific bundles.
There's really no one-size-fits-all solution. :-) There are all sorts of considerations.
Note that HTTP/1.1 made additional HTTP requests more efficient and is nearly universally supported, and HTTP/2 makes them a lot more efficient, effectively eliminating the case for reducing the number of HTTP requests as part of the page startup. All major modern browsers support HTTP/2 as do up-to-date web servers.
Put the code for each page in a function
If you really want to keep a single page, put the code that's specific to each page in functions for those pages, and then have code in the main part of the file call the appropriate function based on location.pathname.
You figured it out, you have to check this for every variable. But generally it's much more convenient to use functions and only call these functions when you need them.
So for example, say you want to set some copyrightYear (even tough this shouldn't be set via JS, you should generate this on Backend side to have it in the source code)
You have something like this:
function updateYear() {
// here you do your magic of selecting the element, setting the year, whatever.
}
// another function, totally unrealted to updateYear()
function toggleMenu() {
// some function where you toggle the menu if you click somewhere
// like: button.addEventListener('click', () => {} );
}
And in your JS file you have one block where you call all these functions:
if (document.querySelectorAll('.elementForYear') {
updateYear(); // here we call it because we are sure this element exists... so everything inside function must work
}
if (document.querySelector('.myMenu') {
toggleMenu(); // if the element myMenu exists, we know we can add these toggle Functionality.
}
You can also add these if inside the function and call the function regardless of if it's needed or not, that's up to you and up to coding guidelines.
Generally I find it makes sense to have one function only rely on one (or max two to three elements if it's some toggling of other elements) ... And then you just check for one element. And if this one element exists you can go ahead and call the function.
I wrote a TestComplete extension to update Keyword-Test signature from TestComplete in to an external tool.
However, this action takes very long time. That's why I need to change the cursor from arrow to hour glass, and back to arrow after action is done.
The module doing the opperation is writen in js.
If I try to use the following code, suggested by TestComplete code completition
Win32API.SetCursor(Win32API.IDC_WAIT);
I got the error "Object expected". I.e., the js in the TestComplete extension does not know About Win32API object, despite the code completition suggestion.
Ommiting the Win32API. prefix has the same effect. Trying to create appropiate object via
new ActiveXObject("SomeKindClass")
fails, because I am not able to find appropiate name for the class containing some methode to change cursor. (I tryed, Microsoft.Win32, Microsoft.Win32API, Win32, Win32API and some other non-sence names...)
SmartBears description on writing extentions seems to contain no hint about changing the cursor in a js ScriptExtension.
Please appologize, if I overlook it.
Any suggestions are appreciated. Thanx in advice for your F1!
Edit:
A possible way to solve this is described bellow. However, I am not able to follow it to the end, because of lack of time. Perhaps someone can confirm or deny the correctness. That' would be great.
Steps:
Create a new vbs module in the ScriptExtension (or extend an existing one if any).
In
the init method of vbs module, load user32.dll, create prototypes for
the LoadCursor and CreateCursor methods of user32.dll. (See Tutorial)
You call those methods in your setCursor2* methods.
Declare the setCursor2* methods in the Description.xml as method in RuntimeObject of your namespace (See Script Extension Description file)
Call the methods in the js module YourNameSpace.setCursor2Hourglass(); and YourNameSpace.setCursor2Arrow(); respectively.
It is impossible to show an hour glass from a TestComplete extension.
The reason is, following quote, that comes from https://support.smartbear.com/testcomplete/docs/working-with/extending/script/creating/specifics.htm.
"If your design-time action performs actions against the TestComplete
main window (for example, minimizes the window, changes the window
size and position, and so on), an error will occur. This happens due
to specifics of thread organization in TestComplete."
I guess, the "on so on" part includes changing the cursor…
My Shopify app replaces the product pages 'Add To Cart' form / product form, with it's own form of sorts. It is Liquid logic that decides whether or not to render the entire <form> element.
This works great, but on some themes (like Jumpstart by Shopify), the product page bugs out completely, throwing me an error saying:
option_selection.js - can't access its "parentNode" property
Which I believe is the option_selection.js function where it is looking for the select box / variant ID somewhere on the page.
Of course, this variant ID / select box does not exist because it is not being rendered.
How can I replace the add to cart form while satisfying the option_selection.js functions?
Usually this wouldn't be a big deal, but Shopify's app review team will give me problems with this, and on the Jumpstart theme specifically, this error causes the product photos to not render; breaking the page completely.
Any ideas here? Much appreciated!
Axing the entire product form seems a bit extreme - there's no way to do what you need to do in a less invasive way?
Assuming not, you'll want to expand your install so that you can update any code in a theme that initializes the product form to take into account the possibility that you've defied the theme's simplistic assumptions.
For the option_selection.js compatibility, you'll be looking for where new Shopify.OptionSelectors is being invoked. If your code has set a variable through Javascript, that may be the easiest check to make. Example of an inline install that assumes your code creates a function named MyAppNamespace.isProdHidden:
Original:
new Shopify.OptionSelectors( ...
Updated:
!(window.MyAppNamespace && MyAppNamespace.isProdHidden({{ product.id | json }}) ) && new Shopify.OptionSelectors( ...
The added piece of code will evaluate to false if and only if your app has loaded properly and your isProdHidden function returns a truthy value. This scenario would prevent the new Shopify.OptionSelectors part from running, since we're using the && as a sort of short-circuit/emergency-stop operation.
If your app failed to load (or was uninstalled from the store without the liquid code being updated), or if MyAppNamespace.isProdHidden returns false, then the added block of code evaluates as true and the new Shopify.OptionSelectors happens as normal.
The above is equivalent to wrapping the entire new Shopify.OptionSelectors call in an if statement, with the install benefit that the party installing your app doesn't need to read the theme code to figure out where the OptionSelectors call ends. In most themes the OptionSelectors code is spread out over multiple lines and occasionally theme developers declare their onVariantChange function as an inline anonymous function - neither of which are big obstacles for experienced developers, but a huge complication for novices and store owners without this kind of expertise.
Making the status of your app available somehow through Javascript is probably the best thing for you to do as far as theme-install-compatibility goes. Some themes have their OptionSelectors call right in the product page, which can be affected by dynamic Liquid variables, but many have this code tucked away in a .js file in the assets folder instead. Still other themes don't use Shopify's OptionSelectors code at all and instead run their own thing, and thus your app could interfere in completely unexpected ways or places. Creating tools to make it easier to integrate your app into somebody else's code is therefore one of the best things you can do.
You'll also want to make sure that your code is able to handle multiple products, as many stores have quick-shops all through the site which can load arbitrary product forms. By making sure you have made the tools available, it's possible for you, your support team (if any) and theme devs can make the required updates to (almost!) any arbitrary theme.
Hope that helps!
When developing a web app with jQuery or normal JavaScript, it is commonplace to check for feature availability first. So for example, if I want to use the document.oncopy event, I should first have something like this to ensure my code doesn't break for lesser browsers:
if ("oncopy" in document) {
// Feature is available
}
I'm a bit puzzled about how this would work in Angular2. I could still use the same if I expect to only run in the browser, but I'm specifically told to leave the DOM alone if I want to use Angular Universal and depend on templates or the DomRenderer instead. This allows the page to be pre-rendered on the server and provides a truly impressive performance gain.
But suppose I want a specific div to be invisible if the document.oncopy is unavailable. My understanding is that this is not recommended:
<div *ngIf="hasFeature()">...</div>
and
hasFeature() {
return 'oncopy' in document;
}
because then I'm still manipulating the DOM. Note that my example is about the document.oncopy but I could choose any feature whatsoever that doesn't have universal support.
I tested this using Chris Nwamba's tutorial on Scotch and added the following to the end of his Home template:
<div *ngIf="hasFeature()">Feature is supported</div>
<div *ngIf="!hasFeature()">Feature is NOT supported</div>
Update: Interestingly, it gave different results on different browsers. On Chrome 55, it executed as it would normally and showed the "Feature is supported" message. On IE11, I received the "not supported" message. In both instances the server log shows a EXCEPTION: document is not defined message, but the page still seems perfectly okay.
So what is the correct way to check for browser features if I want to use Angular Universal?
Update:
I also toyed around with using a field in the template and assigning that field from one of the life cycle hooks. ngAfterContentInit seemed like a fine candidate, but also causes an error on the server. It still runs fine in the browser with no weird effects (that I have noticed so far).
There are two ways to approach this:
Do the check only once the server is done rendering and the client is completely initialised (including the replay of user events done by preboot.js).
Return a reasonable default when the page is running on the server and perform the actual check only in the browser.
I started looking at the first option, but none of the Angular2 life cycle events will help with this. In fact, you can clearly see them all executing on the server and only then on the client.
I then started looking for something usable in preboot.js but quickly realised it was more complex than it needed to be.
So onto option 2 I went. It turns out checking for the browser is as easy as importing and checking isBrowser.
import { isBrowser } from "angular2-universal";
#Component({
// All the usual stuff
})
export class MyComponent {
// ...
hasFeature(): boolean {
return isBrowser && 'oncopy' in document;
}
// ...
}
And then use the template as I showed in the question.
To check if you're running on the server, import and use isNode in exactly the same way. There doesn't seem to be an obvious way to distinguish between Node and ASP.NET Core, but perhaps it's best not to write too much code that specific to platform.
this questions pops up again and again across the internet (even on SO), but I haven't found a satisfying solution to this problem:
How can we change/replace Javascript code in a running web application, without reloading the page?
Many people answer this with "you cannot, because it is impossible". Some experiments with IntelliJ IDEAs live edit plugin proves me that it is possible. But I don't want to be bound to an IDE for this feature. (Bonus: browser independent)
Here is what I tried:
add //# sourceURL=whatever.js to my dynamically loaded script
add folder to Chrome containing whatever.js
mapping the local whatever.js to the network whatever.js
changing code in either does not affect the web-page at all. In fact editing the network-side file results in a oddish "flashing" of the dev tools.
Please understand that I do not expect the changed JS to magically apply to the webpage once I change it, but I expect it to use the new code when the execution point is passed again.
Example:
Given a button that triggers 'alert(1);'
Change to 'alert(2);'
I expect the button to trigger 'alert(2);'
Having many dependencies and a huge script that is triggered pretty late in a workflow it is really a big problem for me to refresh the page, so I need to find a solution that works on-the-fly.
First of all: What you ask for is really tricky and you can find security problems if you allow this in your applications, anyway it is not impossible.
BUT if you want to achieve your example follow this steps:
Make a code snippet like this:
var message = "1"; // this must be a global variable!!!!
function showMessage() {
alert(message);
}
Given a button that triggers 'alert(1);'
Make button call a function ie: onclick='showMessage()'
Change to 'alert(2);'
I expect the button to trigger 'alert(2);'
Now it's easy, When you detect the event that implies to change the alert message to 2 you just need to change message value:
message = "2";
That's all.
Option 1: Livereload
I would say as long it's for develop reasons you can use livereload on your server.
Depends of your server type. I'm note big expert in apach, glassfish and other java's world stuff, but in world of JS (nodejs) this is a shorter way.
(link for npm-livereload)
Hack: You can handle static-files such as js, css with simple node.js server with built-in livereload.
Option 2: jRebel
I'm not sure about js but perhaps JRebel can handle this issue. Anyway it's a good addition to the develop process - at least it would make a java's "hot reload: for you.
Option 3: Monkey-patching
You can use monkey-patching techniques: Each function in js it's just a string, you can turn string -> function with new Function().
just like:
var foo = {
sum: function (a, b) {return a+b;}
}
//...
obj.sum = new Function(....) //Now you're replaced the original code
check this article about graceful way to do monkey-patching.
And small advertising of my lib for monkey-patching: monkey-punch
Option 4: Attach new tag
You can attach js files with:
var s = document.createElement("script");
s.type = "text/javascript";
s.src = "http://somedomain.com/somescript";
$("head").append(s);
You're also able to remove dom elements (scripts, styles) and attach new at anytime.