Opera Unite: Convenient tips and snippets for developers

Well, as I’m keeping on with my experiments with Opera Unite I continue to make notes, and perhaps some other developers out there might come across the same issues as well.

Here is how to implement “data-if”, some debugging-tips and a patched Markuper amongst others.

Introducing a basic “data-if” in Markuper

The Markuper has a great, simple method of adding custom attributes to determine whether or not keep an arbitrary element and it’s content. You may already know the “data-keep-if“-attribute, now – here is “data-if“, a tad more flexible:

/**
Author: Michael Odden
Email: me@michaelodden.com

A simple example of an extension of Opera's Markuper - a Unite JavaScript template-library:
Implementing the "if"-operator, allowing for boolean expressions, and does only show
the element if the value is true.

To allow variables in the expressions, you'll have to download the patched Markuper, currently found at:
http://michaelodden.com/files/unite/template.js


See blogpost for details:
http://michaelodden.com/opera-unite/opera-unite-convenient-tips-and-snippets-for-developers

NB! You should not include data in the attribute which you don't controll 100%, as the method
is currently using "eval". Yup, bad, I know. I'm working on a better approach.

= USAGE:

- In the server side JavaScript, where you initiate the Markuper:

var data = {"sum":101};
var template = new Markuper("path/to/template.html", data);
template.registerDataAttribute('if', dataAttrIf);


- In the template:
<div data-if="{{sum}}>100">
<p>The sum is more than 100</p>
</div>

*/

function dataAttrIf(node, data, key) {
	node.removeAttribute("data-if"); // Remove the attribute, leaving no trails
	if(!eval(key)) {
		node.remove(); // remove the node
		return false; // avoid processing the contents of the node
	}
}

NB! I’m currently using JSeval-method, which – in short – is not recommended if you don’t have 100% control of the code being evaluated. While it gives you extreme flexibility, it will probably also affect security. It is amongst others a very common source of XSS-vulnerabilities.

I am working on a better expression-parser, perhaps built upon Markuper’s “operatorsRegExp”, which is used for some neat interpretation of the “data-keep-if”-values. As far as possible I will for now recommend data-keep-if as long as it’s possible, and this is mostly an example of how easy it is to expand the Markuper, and making a little more flexible option needed for an application of mine.

You will also need the patched Markuper found at the bottom of this post (template.js) if you want to allow variables in the expressions. See my previous Unite-post for details.

Check if files exists

I came across a situation where I preferred to use the _request event-handler, which is a “fetch-all” event, this mean that I won’t need to dispatch events for every single action, but rather take care of the logics in one gateway-method. But there is a problem: when using the _request-handler, Unite won’t dispatch through to any publicly available files either, unless explicitly told so. In my case this meant my .css og client-side .js-files wasn’t loaded.

To work around this, I created a function which checks if there is a file matching the current request – and if so serve this instead of continuing with the regular logic.

Remember to include access to the file i/o by adding the following in your config.xml (inside the widget-element):

    <feature name="http://xmlns.opera.com/fileio"></feature>
/**
 * @author	Michael Odden
 * @email	me@michaelodden.com
 * @url		http://michaelodden.com/
 * @description
 * 		Checks if the requested path can be mapped to an existing static file (underneath public_html).
 *		If so: server the static file, otherwise continue the normal flow. Intended used with the _request event handler.
 * 				
 * 
 * @return	mixed - true / false regarding if file exists, or the result of response.closeAndRedispatch ()
 * @param	OperaEvent - e - The event-object as sent to the eventhandler (required)
 * @param	boolean - dispatch - should the method autmaticly dispatch to load the file, or just return true / false.
 */
function requestedFileExists(e, dispatch) {
	var fileExists = true;
	var uri = e.connection.request.uri;
	var dir = opera.io.filesystem.mountSystemDirectory('application');
	dispatch = typeof(dispatch)!="undefined" ? dispatch : true;
	
	if(dir) {
		try {
			var relPath = uri.replace(new RegExp(e.target.currentServiceName,"i"), "public_html");
			var stream = dir.open(dir.resolve(relPath ), opera.io.filemode.READ);
		} catch(err) {
			fileExists = false;
		}
	}
	
    return (dispatch && fileExists) ? e.connection.response.closeAndRedispatch() : fileExists;
}

I found it necessary to also have a function which simply checks if there is a public file with a given name, and simply returns true or false.

/**
 * @author	Michael Odden
 * @email	me@michaelodden.com
 * @url		http://michaelodden.com/
 * @description 
 * 		Checks if a given file exists under the public_html folder. Returns true / false accordingly.
 * 		
 * 
 * @return	Boolean - true / false regarding if file exists
 * @param	OperaEvent - e - The event-object as sent to the eventhandler (required)
 * @param	String - file - path relative to public_html to look for
 */
function publicFileExists(e, file) {
	var fileExists = true;
	var dir = opera.io.filesystem.mountSystemDirectory('application');
	
	if(dir) {
		try {
			var relPath = "public_html/" + file;
			var stream = dir.open(dir.resolve(relPath ), opera.io.filemode.READ);
		} catch(err) {
			fileExists = false;
		}
	}
	
	return fileExists;
}

Usage

window.onload = function() {
	var w = opera.io.webserver;
	if(w) {
		w.addEventListener("_request", onRequest, false);
	}
}

function onRequest(e) {
	// Demo 1: requestedFileExists()
	// Check if the requested URI can match to an existing file under public_html.
	// If so, dispatch directly to that file, and cancel further load
	requestedFileExists(e);
	
	// Demo 2: publicFileExists()
	// this demo as a whole does the same as Demo 1, but the publicFileExists does
	// only return true / false, and the developer has to decide action herself.
	if(publicFileExists(e, "tesfile.html")) {
		e.connection.response.closeAndRedispatch();
	}
}

Notes to self

window.onload is only run when the application is started (through the Unite Control Panel), and not on each page-load as some would believe.

Markuper does of course need access to the filesystem.

Try-catch is my best friend. If you have some weird situations where your application doesn’t load, it might be because a missing library (not including the file-io through your config.xml etc) then you’ll have to start debugging. By wrapping essential parts in try-catch and check the error-object as passed to the catcher you might save yourself quite some time debugging in the darkness. It is a matter of best-practice to use try-catch on parts of your code that might fail, but it will also come handy as a debugger tool just for checking large or small parts of your code.

JavaScript on both sides

It seems to be a common source of confusion for some: it’s important to remember that – in Unite – JS is used both on the server-side as well as on the client-side. Methods like “alert()”, won’t work on the SSJS (the ones included in the main index.html-file, either directly or indirectly). Pretty obviously, but I remember my first debugging-action was to try an “alert()” ;).

Recommended

  • template.js – patched version of the Markuper, fixing the bugs as mentioned in my previous Unite-article. Thanks goes to Ant贸nio.
  • Offical Opera Libraries – Opera does have some really skilled developers, and it’s worth checking out the libraries.
  • JavaScript for hackers – a great article, and the title says it all – well worth read for everyone working with JavaScript.
Be Sociable, Share!

8 Comments

  1. Ant贸nio Afonso says:

    Hi,

    Regarding the “operatorsRegExp”, it’s quite different now (haven’t released yet because I need to finish the documentation), and it will support more operations/numbers/strings in the next release.
    The library is still under construction, we are adding new features as we need it.

  2. […] Opera Unite: Convenient tips and snippets for developers … Social […]

  3. michaelo says:

    Hi Ant贸nio,

    That sounds great, I’m looking forward to the next release. Do you have any estimates regarding when this will be? As I’d like to know if it’s worth it’s time developing my own version, or just stick with my simple method, which will do during development.

  4. Ant贸nio Afonso says:

    Michaelo,

    It seems that you’re already looking at the latest version, I thought you were using the one documented at dev.opera.com.
    Isn’t the markuper.evaluator.operators array enough for you to add new operators? I wanted to have a very simple and easy-to-grasp expressions support but I can change it depending on developers feedback, so any feedback on this subject is most welcome 馃檪
    I can’t say when the next release is coming out, the one with strings and numbers support, and some other goodies. But I’ll (most likely) only be working on it after the final release.

  5. michaelo says:

    Hehe, OK, I dissected the Fridge-app, I believe, in hope to find a version which eliminated some issues 馃檪

    I added some operators, but my operators are only useful when we allow other values than true/false, so a quick hack which made it work for me was to replace:

    “return !!markuper.getData( data, key )” in markuper.evaluator.getBooleanValue, with:

    return (markuper.getData( data, key )) ? !!markuper.getData( data, key ) : key;

    – the method will then first will look for booleans, otherwise just return the key itself, which now only will be the value to compare. The problem here is that it’s now three kinds of data to use in the attribute-values:

    1. {{var}}, which will be replaced to it’s corresponding value – then treated as 2 or 3.
    2. “var” which if matches a key in the dataset will be set to true/false
    3. “var” which if not matches a key will be used as-is

    So my thougts right now is that you should be consistent in using the {{var}}-format when dealing with variables, which in turn could be true or false to allow for easy checks – and otherwise treat the values as actual data.

    This worked well when I implemented some operators through evaluator.operators, like “>”, “< ", "==", ">=”, “!=” etc. What do you think?

    What do you think?

  6. Ant贸nio Afonso says:

    I added some operators, but my operators were only useful when we allow other values then true/false, so a quick hack which made it work for me was to replace:

    鈥渞eturn !!markuper.getData( data, key )鈥 in markuper.evaluator.getBooleanValue, with:

    return (markuper.getData( data, key )) ? !!markuper.getData( data, key ) : key;
    uhm… then it stopped being a getBooleanValue right? 馃槈

    The problem here is that it鈥檚 now three kinds of data to use in the attribute-values:

    1. {{var}}, which will be replaced to it鈥檚 corresponding value 鈥 then treated as 2 or 3.
    2. 鈥渧ar鈥 which if matches a key in the dataset will be set to true/false
    3. 鈥渧ar鈥 which if not matches a key will be used as-is

    So my thougts right now is that you should be consistent in using the {{var}}-format when dealing with variables, which in turn could be true or false to allow for easy checks 鈥 and otherwise treat the values as actual data.

    I’m not really sure what you mean with “you should be consistent in using the {{var}}-format when dealing with variables”…
    The 1. and 3. point only happens because you changed the code right? 馃槈
    But I think I’m missing something here, can you please comment to my e-mail? it would be much easier for me to follow up and reply 馃檪

    This worked well when I implemented some operators through evaluator.operators, like 鈥>鈥, 鈥<", "==", "=鈥, 鈥!=鈥 etc. What do you think?

    I wouldn’t implement the “=” myself but the other ones I was going to of course, just didn’t have the time at the time (yeah yeah, I know it’s just a few lines of code but I also create unit tests and more documentation, etc.)

  7. Ant贸nio Afonso says:

    I tried using [quote] but the comment was reject,replaced them for <quote> but failed miserably 馃檪

  8. michaelo says:

    Ant贸nio: I wouldn鈥檛 implement the 鈥=鈥 myself

    The “=” was a typo, should been “>=” 馃檪 Ah, thanks for the notice, added a list of allowed tags/elements under the comment-area now – and edited your comment.

    Ant贸nio: But I think I鈥檓 missing something here, can you please comment to my e-mail?

    Yeah, I’ll send you an email so we can discuss it further with more ease.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>