Where do I put onWindowBeforeUnload? - javascript

I have a SAP UI5 application and I would like to show a box that appears when the user tries to navigate away from the screen through methods outside my application (clicking on links, manually changing URL, etc).
I see there is a onWindowBeforeUnload() method but I am not sure where I put this method or how it gets called. I tried including it in my controller that I want the functionality in but it does not get called when I navigate away. If there's any other function that can provide this behavior, that'll be fine too.
Here's what I did in my controller:
onWindowBeforeUnload: function() {
alert("you sure?");
},
I see that this is a method of the component class but I thought this was created in the beginning; I am not too familiar with this concept.

I don't think you can override it directly from your controller. You'll either have to override it on the window object itself (as per #matt-spinks' answer above) or override it on your Component.js file (if you are using one).
Here's how to do it on the Component.js file:
sap.ui.define(["sap/ui/core/UIComponent"], function (UIComponent) {
"use strict";
return UIComponent.extend("company.main.Component", {
// ...
// ...
/**
* Fired before the window closes or moved to another URL
*/
onWindowBeforeUnload: function(oEvent) {
// your code
},
/**
* Fired when the window is closed.
*/
onWindowUnload: function(oEvent) {
// your code
}
});
});

There are a couple different ways of doing it:
in the body tag
<body onbeforeunload='alert("no!!")'>
in your js code:
<script>
window.onbeforeunload = function() {
alert('no!!');
}
</script>
Make sure to use window.onbeforeunload and not onWindowBeforeUnload.
Also, make sure you put the code within tags in the rendered output of your page, or inside an included javascript file. Based on what your code looks like, it sounds like you are using a javascript library, which is why you are using functionName: function(){ //body }. You cannot use that method to override the window.onbeforeunload function, because you are trying to handle the function inside a sub-node of window. You have to use the top-level window node and handle the function there. And you can do that by putting this code directly inside <script> tags or the body tag.

Related

Manually Invoke IIFE

I've got a library (Hubspot Odometer) which I am using in a web application I am developing and it works fine to create and run Odometer style widgets on a page.
The trouble is that they are part of a dashboard interface which has panes that are loaded via AJAX. The initial view is not loaded via AJAX so the JavaScript executes fine and the odometers render correctly.
When I load a new pane with odometers however they are not rendered correctly nor do they act as they should. The reason for this is that the odometer library runs as one big IIFE.
What I am wondering is can I re-invoke the IIFE manually after I load content via AJAX so that the odometers are rendered and bound to correctly?
I am also using jQuery if that offers me any additional options.
Try this:
var funcName = (function funcName() {
// rest of the code
return funcName;
}());
Also see this jsbin.
The whole idea of the IIFE is that its an anonymous function that is immediately executed. So by definition, no there is no way to re-execute it.
With that said however, you can store the function expression to a global variable, and execute it. For example
window.my_iife = (function() { /* stuff */ });
window.my_iife();
Notice the slight difference in syntax compared to the traditional IIFE: ((function() {})());
By storing the function in window, you are able to access it later from either the developer console or anywhere else in your code. If you simply store it in a var, or declare it as function my_iife() { /* ... */ } somewhere you risk that var or function declaration itself being wrapped in an IIFE and thus being inaccessible. As an example, that scenario could occur if the file you declared your var/function in is part of a Sprockets manifest (such as application.js in Rails).
var funcName = null;
(funcName = function funcName() {
// rest of the code
return funcName;
}());
funcName();
This is what worked for me

How can I clear references to Javascript functions that no longer exist?

I am working on a project that uses AJAX to download HTML, CSS and Javascript in one singe chunk of text then appends it to an element on the page. Here is the code:
_t.stage.empty();
_t.stage.html(DATA);
This works fine.
Here is the problem:
After adding the HTML to the stage, I call this function:
if(initApp != null && typeof(initApp) == "function") initApp();// Checks for initApp(). If exists, executes.
If I load a page that has this function, then load one that does NOT have this function, the function from the first page is executed. Here is some psuedo code to understand the results.
page 1:
This is a page.
<style>...</style>
<script> function initApp(){ alert("hello"); } </script>
When this page is run, an alert box with the text 'hello' is shown.
page 2: (no initApp() function)
This is page 2.
<style>...</style>
When the page is run, an alert box with the text 'hello' is shown.
Please note: These pages are loaded with AJAX and inserted into the HTML of an already loaded page.
It is not easy to tell exactly what you're trying to do, but if what you're trying to do is make it so that some other code that calls initApp() will cause nothing to happen when it calls that, then you can simply redefine the function to a do-nothing function like this:
initApp = function() {}
The most recent definition of a function takes precedence (e.g. replaces any prior definitions).
If your newly loaded code contains an implementation of initApp() that you don't want called the second time the script is loaded, then you're out of luck. You can't stop that. You will need to change the structure of your code so that the dynamically loaded code doesn't execute stuff you don't want to be executed. There are many different ways you could do that. For example, you could have a global boolean that keeps track of whether the init code has been called yet.
var initCalled = false;
function initApp() {
if (!initCalled) {
initCalled = true;
// rest of initialization code here
}
}
initApp(); // will only actually do anything the first time it's called
// even if it is loaded more than once
It appears from the comments that you seem to think that reloading a script tag with different code will somehow make code from the previous script go away. It will not. Once a function is loaded, it stays loaded unless it is redefined to mean something else or unless some code explicitly removed a property from an object. It does not matter how the code was loaded or whether it was in the core page or an external script file.
Javascript functions that no longer exist
This is a bad premise. The functions still exist, which is obvious from the fact that the second AJAX load ended up executing it. The fact that the <script> tags are replaced and no longer in the document doesn't undefine the function. It's like asking why is your TV still broken if the burglar that broke it is no longer there.
There are two basic things you can do:
a) Clear the function explicitly yourself:
if (initApp != null && typeof(initApp) == "function") {
initApp();
delete window.initApp;
}
b) Change the function name to be unique per AJAX page (or namespace the function with the same idea), probably tied to the name of the AJAX page, so you can invoke it in a more specific manner.

setReadOnly causes error when called on instanceReady of CKEditor

I'm trying to set my CKEditor instance to be "readOnly" after the instance has fully loaded but I'm getting a Javascript error: Cannot call method 'setReadOnly' of null. When I dig into it, the error is coming from this line in the ckeditor.js, within the editor.setReadOnly method: this.editable().setReadOnly(a); That means that the editor exists, but the editable method/attribute (on the CKEditor instance) does not.
Below is my code, and I'll explain it a little. My app is a combination of GWT and Backbone. The CKEditor itself is created by the Backbone code but the parent element is in GWT so that's where I initiate the setEnabled action.
private native void setEnabledOnLoad(boolean enabled, String id) /*-{
CKEDITOR.on("instanceReady", function(evt) {
if(evt.editor.name === id) {
Namespace.trigger(Namespace.Events.SET_ENABLED, enabled);
}
});
}-*/;
setEnabled: function(enabled) {
this.editor.setReadOnly(!enabled);
if(enabled){
this.editor.focusManager.focus();
} else {
this.editor.focusManager.blur();
}
}
The Backbone class has a listener for Namespace.Events.SET_ENABLED that triggers setEnabled.
Is there another CKEditor event that I should listen for? There doesn't appear to be an instanceReady event on editable. What am I missing?
EDIT
this.editor is created in the Backbone class render function like this:
this.editor = CKEDITOR.replace(this.$(this.id)[0], config);
The reason I don't add the instanceReady listener right after it's created is because the function setEnabledOnLoad is called in GWT before the instance has been fully initialized. This is a result of having the code in two places. GWT has said "ok, create the instance" but Backbone hasn't finished by the time GWT goes to the next line of code and wants to set it enabled/disabled.
Two years later, but here is my solution. Maybe someone else will find it useful.
As stated above, the event is appearantly triggered before the editable() function is fully set up, and therefore one solution is to simply wait for it to finish before setting it to readonly. This may be an ugly way to do it, but it works.
//Delayed execution - ckeditor must be properly initialized before setting readonly
var retryCount = 0;
var delayedSetReadOnly = function () {
if (CKEDITOR.instances['bodyEditor'].editable() == undefined && retryCount++ < 10) {
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
} else {
CKEDITOR.instances['bodyEditor'].setReadOnly();
}
};
setTimeout(delayedSetReadOnly, 50);
You could try subscribing to instanceReady event this way:
CKEDITOR.instances.editor.on("instanceReady", onInstanceReadyHandler)
However, the editor instance must have been already created by then (inspect CKEDITOR.instances in the debugger).
I'm a bit confused about the difference between editable and editor. Could you show the fragments of your code where this.editor and this.editable get assigned?
[EDITED] I guess I see what's going on. CKEDITOR is a global object, you may think of it as of a class which holds all CKEDITOR instances. Trying to handle events with CKEDITOR.on isn't right, you need to do it on a specific instance (like I've shown above). I assume, "editor" is the ID of your parent element you want to attach a CKEDITOR instance to (please correct me if I'm wrong). I'm not familiar with Backbone, but usually it's done with replace:
var editorInstance = CKEDITOR.replace("editor", { on: {
instanceReady: function(ev) { alert("editor is ready!"); }}});
Here we attach a new instance of CKEDITOR to the editor parent element and subscribe to the instanceReady event at the same time. The returned object editorInstance should provide all the APIs you may need, including setReadOnly. You could also access it through the global CKEDITOR object using the parent element ID, i.e. CKEDITOR.instances.editor. On the other hand, editable is rather a service object available on editor. I can't think of any specific case where you might need to use it directly.
I apologize for never updating this with my solution. I needed to decouple the GWT function further from the CKEditor behavior. So, I added a function in GWT 'setEnabled' that is called from the parent object when it wants to update the enabled state of the CKEditor object.
public void setEnabled(boolean enabled) {
this.enabled = enabled;
toggleCKEditorEnabled(enabled);
}
Then changed the function referenced above 'setEnabledOnLoad' to be 'toggleCKEditorEnabled' which triggers the SET_ENABLED event with the enabled value.
Instead of attaching the listener to the specific instance of CKEditor, I added in to the Backbone MessageEntryView class that is the container of the CKEditor instance. In the initialize function of the MessageEntryView, I added this line
Namespace.on(Namespace.Events.SET_ENABLED, this.setEnabled);
This only works because I have one instance of CKEditor loaded on the screen at any given time. This problem and its solution stopped us from being able to add more CKEditor instances to the page at a time, which is something we discussed before moving on and replacing our whole client with Backbone.

extend a javascript option to add functionality

I need to call "MyOtherFunction" when "MyFunction"(which creates an element) completes, without MyFunction knowing what MyOtherFunction is.
The reason I need this is for extension of a jquery powered fileupload User Control that is used in several places with different functionality. A specific page shows a header and file count for it, and when the upload completes, I need to modify the file count according to how many files are displayed(by created elements) I thought :
$(UserControl).on(MyFunction, UploadElem, MyOtherFunction);
but this route is not accomplishing anything. The most I can alter the User Control is add in a function call, but without effecting the original user control functionality.
I'm not sure if because MyFunction isn't an event and doesn't bubble up or if it just isn't possible to use a defined function as a parameter of .on() is the reason I cannot get this code to work. Any suggestions?
Easiest way I can think of, is duck punching respectively hooking that method:
var _oldMyFunction = MyFunction;
MyFunction = function() {
_oldMyFunction.apply( this, arguments );
MyOtherFunction();
};
I managed to solve my own issue, but the context is important for the answer:
// Using a Global JavaScript object I created:
GlobalNameSpace.ExtensionFunction = function(oParam1, oParam2, oParam3)
{
/// <summary>All parameters are optional</summary>
return; // For instances when it is not being overwritten, simply return
}
//In the Code for the user control:
GlobalNameSpace.UploadControl.UploadComplete(oSender, oArgs)
{
///<summary>Handles the Upload process</summary>
// process the upload
GlobalNameSpace.ExtensionFunction(oSender, oArgs);
}
//and finally in the code to extend the functionality
GlobalNameSpace.Page.Init
{
///<summary>Initializes the page</summary>
// redefine the extension function
GlobalNameSpace.ExtensionFunction = function(oSender, oArgs)
{
GlobalNameSpace.Page.Function(oSender, oArgs);
}
}
This allows me to extend anything I need it to without polluting my objects, and having something generic already existing to call on to make my changes. This solution solves my problem of needing a onCreate function for the elements I create to represent my uploaded items to trigger the header displaying the number of files. Very useful

How does alfresco's javascript( not webscript) mechanism

When I play with alfresco share, I found it is difficult to track the UI and javascript. you can only see some class name in the HTML tags, But you are difficult to know how are they constructed, And When, where and how can these scattered HTML code can render such a fancy page.
Can someone help me ? Please offer several example and explain how they work!
Thanks in advance!
Here is some example that will hopefully help you (it's also available on Wiki). Most of the magic happens in JavaScript (although the layout is set in html partly too).
Let's say you want to build a dashlet. You have several files in the layout like this:
Server side components here:
$TOMCAT_HOME/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets/...
and client-side scripts are in
$TOMCAT_HOME/share/components/dashlets...
So - in the server side, there is a dashlet.get.desc.xml - file that defines the URL and describes the webscript/dashlet.
There is also a dashlet.get.head.ftl file - this is where you can put a <script src="..."> tags and these will be included in the <head> component of the complete page.
And finally there is a dashlet.get.html.ftl file that has the <script type="text/javascript"> tag which usually initializes your JS, usually like new Alfresco.MyDashlet().setOptions({...});
Now, there's the client side. You have, like I said, a client-side script in /share/components/dashlets/my-dashlet.js (or my-dashlet-min.js). That script usually contains a self-executing anonymous function that defines your Alfresco.MyDashlet object, something like this:
(function()
{
Alfresco.MyDashlet = function(htmlid) {
// usually extending Alfresco.component.Base or something.
// here, you also often declare array of YUI components you'll need,
// like button, datatable etc
Alfresco.MyDashlet.superclass.constructor.call(...);
// and some extra init code, like binding a custom event from another component
YAHOO.Bubbling.on('someEvent', this.someMethod, this);
}
// then in the end, there is the extending of Alfresco.component.Base
// which has basic Alfresco methods, like setOptions(), msg() etc
// and adding new params and scripts to it.
YAHOO.extend(Alfresco.MyDashlet, Alfresco.component.Base,
// extending object holding variables and methods of the new class,
// setting defaults etc
{
options: {
siteId: null,
someotherParam: false
},
// you can override onComponentsLoaded method here, which fires when YUI components you requested are loaded
// you get the htmlid as parameter. this is usefull, because you
// can also use ${args.htmlid} in the *html.ftl file to name the
// html elements, like <input id="${args.htmlid}-my-input"> and
// bind buttons to it,
// like this.myButton =
// so finally your method:
onComponentsLoaded: function MyDaslet_onComponentsLoaded(id) {
// you can, for example, render a YUI button here.
this.myButton = Alfresco.util.createYUIButton(this, "my-input", this.onButtonClick, extraParamsObj, "extra-string");
// find more about button by opening /share/js/alfresco.js and look for createYUIButton()
},
// finally, there is a "onReady" method that is called when your dashlet is fully loaded, here you can bind additional stuff.
onReady: function MyDashlet_onReady(id) {
// do stuff here, like load some Ajax resource:
Alfresco.util.Ajax.request({
url: 'url-to-call',
method: 'get', // can be post, put, delete
successCallback: { // success handler
fn: this.successHandler, // some method that will be called on success
scope: this,
obj: { myCustomParam: true}
},
successMessage: "Success message",
failureCallback: {
fn: this.failureHandler // like retrying
}
});
}
// after this there are your custom methods and stuff
// like the success and failure handlers and methods
// you bound events to with Bubbling library
myMethod: function (params) {
// code here
},
successHandler: function MyDAshlet_successHandler(response) {
// here is the object you got back from the ajax call you called
Alfresco.logger.debug(response);
}
}); // end of YAHOO.extend
}
So now you have it. If you go through the alfresco.js file, you'll find out about stuff you can use, like Alfresco.util.Ajax, createYUIButton, createYUIPanel, createYUIeverythingElse etc. You can also learn a lot by trying to play with, say, my-sites or my-tasks dashlets, they're not that complicated.
And Alfresco will put your html.ftl part in the page body, your .head.ftl part in the page head and the end user loads a page which:
loads the html part
loads the javascript and executes it
javascript then takes over, loading other components and doing stuff
Try to get that, and you'll be able to get the other more complicated stuff. (maybe :))
You should try firebug for stepping through your client side code.
Alfresco includes a bunch of files that are all pulled together on the server side to serve each "page".
I highly recommend Alfresco Developer Guide by Jeff Potts (you can buy it and view it online instantly).
James Raddock
DOOR3 Inc.

Categories