Javascript – how to make something happen after some time

No Comments »

I’m writing this post because I’ve been asked a million times about this and it’s easier to point someone to a link than to explain it again :-) .

In Javascript, executing something after a specified time couldn’t be easier – you’ve got the setTimeout method. Just be careful to call it the right way. It takes two arguments:

-> the method to be called after the specified time passed. I repeat, a method, not a method call.
-> the amount of time, specified in milliseconds.

For example, using script.aculo.us, I can make the “feedback” div fade away in 10 seconds after rendering like this:

window.setTimeout(function() {Effect.Fade(’feedback’);}, 10000);

(and NOT window.setTimeout(Effect.Fade(’feedback’), 10000}; – also keep in mind Javascript is case sensitive)

Javascript variables scope

No Comments »

A variable can be defined as a segment of memory which holds some kind of value. The programmer can reference that segment of memory by using the name of the variable in order to assign it a value or to read it and use it.

In Javascript, the scope of the variable is the area of the script where that variable is valid to be used. The scope of the global variable is the entire script. That means you can access it anywhere between the <script> and </script> tags and in other scripts that are executed after the variable definition.

A global variable is a variable defined in the main part of the script (not inside a function for instance). It makes no difference if you define it using the keyword var or without it. It does make all the difference in the world if you use it inside a function.

A local variable is a variable that is defined inside a function. It is only available to be used inside that function. Javascript allows you to use the same name for a global variable and for a local one, but when used inside that function, the local variable will be evaluated. A local variable is defined only by using the keyword var. If the keyword is omitted, the variable is defined as global.

Local variables are temporary. Global variables are permanent. Permanent variables exist throughout the execution of all the scripts, until being discarded when the page unloads. Temporary variables are allocated on the stack every time the function is called and deallocated when the execution of the function ends. Large local chunk of data causes the stack to overflow, so data structures like large arrays declared inside a function (as local) or passed to the function (as arguments) should be avoided. Use global variables for such tasks.

Variables in Javascript can only be of 4 types: number (there is no distinction between integer and real-valued numbers), string, Boolean and null values. Remember that Javascript is a “loosely typed language”, which means the type of the variable is not declared explicitly. Javascript implicitly determines the type of the variable based on the initial value that is assigned to it.

The null value is automatically converted to the initial values of other data types once that variable is initialized to a different type – when used as a number, it becomes 0, when used as a string it becomes “” and when used as a boolean it becomes false.

Data types are automatically converted as needed throughout the execution of the script. For instance, when an expression containing a number and a string (like a sum) is evaluated, the value returned is a string. Also, keep in mind that in Javascript evaluation is done from left to right and only paranthesys can change the order of the evaluation:
10 + 10 will return 20
10 + “10″ will return “1010″
10 + 10 + “10″ will return “2010″.

Wicket – Ajax like file upload on a modal window

34 Comments »

A lot of people seem to be asking how to do a file upload using AJAX. The short answer to this is that it cannot be done! That’s because it is not supported by today’s HTML / browser specs.

What you can do is just fake it :-) .

I’ve been using Wicket (Java Web Framework) regularly for web development. Recently I needed to be able to do a file upload inside a modal window. File uploading needs form submission in order to work so I faced a few problems:

  • My modal window was implemented as a panel. I could not do a regular form submission as that would close (submit) my modal window. I’ve been suggested to implement the modal window as a page instead of the panel so that I could use Ajax submit but I really needed it the way it was.
  • Ajax and multipart don’t cope.
  • Whatever I tried, without regular submission, the file uploading field would not return the file name so it was impossible to get.

I searched the web and I finally found and implemented the solution. It involves using an iframe, but it’s pretty simple. Here’s the code if anyone else needs this:

  • UploadIFrame.java
public abstract class UploadIFrame extends WebPage {

    private boolean uploaded = false;
    private FileUploadField uploadField;
    private String newFileUrl;

    public UploadIFrame() {
        add(new UploadForm("form"));
        addOnUploadedCallback();
    }

    /**
     * return the callback url when upload is finished
     * @return callback url when upload is finished
     */
    protected abstract String getOnUploadedCallback();

    /**
     * Called when the input stream has been uploaded and when it is available
     * on server side
     * return the url of the uploaded file
     * @param upload fileUpload
     */
    protected abstract String manageInputSream(FileUpload upload);

    private class UploadForm extends Form {
    	public UploadForm(String id) {
            super(id);
            uploadField = new FileUploadField("file");
            add(uploadField);
            add(new AjaxLink("submit"){
            	@Override
            	public void onClick(AjaxRequestTarget target) {
            		target.appendJavascript("showProgressWheel()");
            	}
            });
        }

    	@Override
        public void onSubmit() {
            FileUpload upload = uploadField.getFileUpload();
            newFileUrl = manageInputSream(upload);
            //file is now uploaded, and the IFrame will be reloaded, during
            //reload we need to run the callback
            uploaded = true;
        }

    }

    private void addOnUploadedCallback() {
    	//a hacked component to run the callback on the parent
        add(new WebComponent("onUploaded") {
        	@Override
            protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
                if (uploaded) {
                	if (uploadField.getFileUpload() != null){
	                    replaceComponentTagBody(markupStream, openTag,
	                            "window.parent." + getOnUploadedCallback() + "('" +
	                            uploadField.getFileUpload().getClientFileName() + "','" +
	                            newFileUrl +"')");
                	}
                    uploaded = false;
                }
            }
        });
    }
}
  • UploadIFrame.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns:wicket>
<head>
<link href = "../counters/css/style.css" type = "text/css" rel = "stylesheet"/>
<!--[if IE]>
<link href = "../counters/css/ie.css" type = "text/css" rel = "stylesheet"/>
<![endif]-->
<script type="text/javascript">
	function showProgressWheel() {
		document.images[0].style.display = 'block';
	  	// delay the wheel a bit so it can locally shine
	  	setTimeout(function() { document.forms[0].submit() }, 800);
	  	return false;
	}
</script>
</head>

<body>
	<form wicket:id="form">
		<table style="width:500px; margin-top:0px;">
	  		<tr>
	  			<td>
	    			<input style="width:auto;" wicket:id="file" type="file"/>
	    		</td>
	    		<td>
					<a wicket:id="submit">Change Avatar</a>
				</td>
				<td>
	    			<img src="images/indicator.gif" style="display:none"/>
	    		</td>
	    	</tr>
	    </table>
  </form>

  <script wicket:id="onUploaded" type="text/javascript"></script>
</body>
</html>
  • UploadPanel.java
public abstract class UploadPanel extends Panel {

    private InlineFrame uploadIFrame = null;

    public UploadPanel(String id) {
        super(id);
        addOnUploadedCallback();
        setOutputMarkupId(true);
    }

    /**
     * Called when the upload load is uploaded and ready to be used
     * Return the url of the new uploaded resource
     * @param upload {@link FileUpload}
     */
    public abstract String onFileUploaded(FileUpload upload);

    /**
     * Called once the upload is finished and the traitment of the
     * {@link FileUpload} has been done in {@link UploadPanel#onFileUploaded}
     * @param target an {@link AjaxRequestTarget}
     * @param fileName name of the file on the client side
     * @param newFileUrl Url of the uploaded file
     */
    public abstract void onUploadFinished(AjaxRequestTarget target, String filename, String newFileUrl);

    @Override
    protected void onBeforeRender() {
        super.onBeforeRender();
        if (uploadIFrame == null) {
            // the iframe should be attached to a page to be able to get its pagemap,
            // that's why i'm adding it in onBeforRender
            addUploadIFrame();
        }
    }

    /**
     * Create the iframe containing the upload widget
     *
     */
    private void addUploadIFrame() {
        IPageLink iFrameLink = new IPageLink() {
        	@Override
            public Page getPage() {
                return new UploadIFrame() {
                	@Override
                    protected String getOnUploadedCallback() {
                        return "onUpload_" + UploadPanel.this.getMarkupId();
                    }

                    @Override
                    protected String manageInputSream(FileUpload upload) {
                    	return UploadPanel.this.onFileUploaded(upload);
                    }
                };
            }
        	@Override
            public Class<UploadIFrame> getPageIdentity() {
                return UploadIFrame.class;
            }
        };
        uploadIFrame = new InlineFrame("upload", getPage().getPageMap(), iFrameLink);
        add(uploadIFrame);
    }

    /**
     * Hackie method allowing to add a javascript in the page defining the
     * callback called by the innerIframe
     *
     */
    private void addOnUploadedCallback() {
        final OnUploadedBehavior onUploadBehavior = new OnUploadedBehavior();
        add(onUploadBehavior);
        add(new WebComponent("onUploaded") {
        	@Override
            protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
                // calling it through setTimeout we ensure that the callback is called
                // in the proper execution context, that is the parent frame
                replaceComponentTagBody(markupStream, openTag,
                        "function onUpload_" + UploadPanel.this.getMarkupId() +
                        "(clientFileName, newFileUrl) {window.setTimeout(function() { " +
                        onUploadBehavior.getCallback() + " }, 0 )}");
            }
        });
    }

    private class OnUploadedBehavior extends AbstractDefaultAjaxBehavior {
        public String getCallback() {
            return generateCallbackScript(
                    "wicketAjaxGet('" + getCallbackUrl(false) +
                    "&amp;amp;newFileUrl=' + encodeURIComponent(newFileUrl)" +
                    " + '&amp;amp;clientFileName=' + encodeURIComponent(clientFileName)").toString();
        }
        @Override
        protected void respond(AjaxRequestTarget target) {
        	UploadPanel.this.onUploadFinished(target, getRequest().getParameter("clientFileName"), getRequest().getParameter("newFileUrl"));
        }
    };
}
  • UploadPanel.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns:wicket>
<wicket:panel>
  <!-- I put the callback snippet at the body so that is rendered for each panel instead of once -->
  <script wicket:id="onUploaded" type="text/javascript"></script>
  <iframe wicket:id="upload" frameborder="0" style="height:60px; width:500px; overflow:hidden"></iframe>
</wicket:panel>
</html>
  • This is how to use it:
		add(new UploadPanel("myUpload"){
			@Override
			public String onFileUploaded(FileUpload upload) {
				if (upload != null){
					if(upload.getSize() > 1000000) {
						return "The Picture Is Too Big !";
					} else {
				        try {
							jdbcSignUp.changeAvatarFor(((WebSession)getSession()).getUser().getUsername(), upload.getInputStream(), (int)upload.getSize());
				        	//save on server side here
				        	//and return the url of the saved file
	                        return "The Picture Has Been Uploaded";
				        } catch (IOException e) {
							log.error("Exception:", e);
						}
					}
				}
				return "";
			}

			@Override
			public void onUploadFinished(AjaxRequestTarget target, String filename, String newFileUrl) {
				//when upload is finished, will be called
				messageLabel.setModelObject(newFileUrl);
				target.addComponent(messageLabel);

				avatarImage.setImageResource(new MyBlobImageResource(((WebSession)getSession()).getUser().getUsername()));
				target.addComponent(avatarImage);
				((BasePage)parent).onAvatarChanged(target);
			}
		});

Most credit should go to Vincent Demay, although his code contains errors.