Best place to attach javascript behaviour to the DOM - javascript

I'm writing a Javascript file that provides the functionality to create certain DOM elements with ease.
Coming from a jQuery background, normally I'd make this file a plugin and set it up with:
(function($) {
$.fn.entryPoint = function(options) {
//do stuff
}
})(jQuery);
I have no desire to use jQuery for this script, however.
What's the best way to attach the behaviour I've just written to the DOM in a fashion that has the same meaning as the jQuery plugin notation presented above?

There is no universal best code for anything. It all depends on what you are trying to do. I might do something like this, which gives a single global variable named entryPoint, enclosed so that nothing else leaks out into the global namespace.
(function(w) {
w.entryPoint = function(options) {
//do stuff
}
})(window);

Related

After remapping jQuery to $ in my WordPress theme, I can no longer trigger functions from outside the js file

I'm working on a WordPress theme with a lot of jQuery in it. By default, WordPress doesn't allow you to use the $ shortcut and you have to use the full jQuery instead - e.g. jQuery('.class') rather than $('.class').
This isn't too much of a hassle over a few lines of code, but I've got a lot now, so I remapped jQuery to $ using:
(function($){
...my functions here...
})(window.jQuery);
This works fine for functions triggered from within that file, but if I use any inline triggers in the PHP, they no longer work. For example:
<a onclick="loadFullPost('<?=get_permalink()?>?ajax=true','<?=$post->post_name?>',<?=$post->ID?>)">Read more</a>
worked fine before remapping but doesn't now. I can't bind the event as usual within the js file, because I won't be able to access the PHP and WordPress functions I need - unless I'm missing something. For example, this wouldn't work:
$( "#target" ).click(function() {
loadFullPost('<?=get_permalink()?>?ajax=true','<?=$post->post_name?>',<?=$post->ID?>)
});
Is there any way around this?
The issue is that your functions are no longer globals. This is a Good Thing™. (See below for why.)
Is there any way around this?
By far the best way would be to not hook up events like that. Instead, keep your code and markup separate, and hook up your functions using jQuery's on and similar. See below for more.
But if you feel you have to, you can make your functions globals by assigning them as properties on window:
(function($) {
window.loadFullPost = function() {
// ...
};
)(jQuery);
or
(function($) {
function loadFullPost() {
// ...
}
window.loadFullPost = loadFullPost;
)(jQuery);
So how would you do
<a onclick="loadFullPost('<?=get_permalink()?>?ajax=true','<?=$post->post_name?>',<?=$post->ID?>)">Read more</a>
...without using a global function? Like this:
<a class="load-full-post" data-permalink="<?=get_permalink()?>" data-ajax=true data-post-name="<?=$post->post_name?>" data-post-id="<?=$post->ID?>">Read more</a>
and then one handler for them
$(document).on("click", ".load-full-post", function() {
var link = $(this);
// Use the values from link.attr("data-permalink") and such
// to do the work
});
Or if you wanted to use your existing loadFullPost function:
$(document).on("click", ".load-full-post", function() {
var link = $(this);
return loadFullPost(
link.attr("data-permalink"),
link.attr("data-ajax") === "true",
link.attr("data-post-name"),
+link.attr("data-post-id") // (+ converts string to number)
);
});
I should mention that you'll get people telling you to access those data-* attributes via the data function. You can do that, but unless you're using the various additional features of data, it's unnecessary overhead (creating jQuery's cache for the data, etc.). data is not an accessor function for data-* attributes, it's much more (and less) than that.
You can also pass your information as JSON:
<a class="load-full-post" data-postinfo="<?=htmlspecialchars(json_encode(array("permalink" => get_permalink(), "ajax" => true, "name" => $post->post_name, "id" => $post->ID))0?>">Read more</a>
(or something like that, my PHP-fu is weak)
Then:
$(document).on("click", ".load-full-post", function() {
var postInfo = JSON.parse($(this).attr("data-postinfo"));
return loadFullPost(
postInfo.permalink,
postInfo.ajax,
postInfo.name,
postInfo.id
);
});
Why making your functions non-global is a Good Thing™: The global namespace is very crowded, particularly when you're dealing with multiple scripts and plugins and Wordpress itself. The more globals you create, the greater the odds of conflicting with one from another script. By having your functions nicely contained inside your scoping function, you avoid the possibility of stomping on someone else's function/element/whatever and vice-versa.
You can add function from your enclosure to window like that:
(function($){
function loadFullPost(...) {
...
}
window.loadFullPost = loadFullPost;
}).(window.jQuery);
Then your function will be visible for onlick attribute etc.

How to re-evaluate a script that doesn't expose any global in a declarative-style component

I have been writing a reusable script, let's call it a plugin although it's not jQuery, that can be initialised in a declarative way from the HTML. I have extremely simplified it to explain my question so let's say that if a user inserts a tag like:
<span data-color="green"></span>
the script will fire because the attribute data-color is found, changing the color accordingly.
This approach proved very handy because it avoids anyone using the plugin having to initialise it imperatively in their own scripts with something like:
var elem = document.getElementsByTagName('span')[0];
myPlugin.init(elem);
Moreover by going the declarative way I could get away without defining any global (in this case myPlugin), which seemed to be a nice side effect.
I simplified this situation in an example fiddle here, and as you can see a user can avoid writing any js, leaving the configuration to the HTML.
Current situation
The plugin is wrapped in a closure like so:
;(function(){
var changeColor = {
init : function(elem){
var bg = elem.getAttribute('data-color');
elem.style.background = bg;
}
};
// the plugin itslef looks for appropriate HTML elements
var elem = document.querySelectorAll('[data-color]')[0];
// it inits itself as soon as it is evaluated at page load
changeColor.init(elem);
})();
The page loads and the span gets the correct colour, so everything is fine.
The problem
What has come up lately, though, is the need to let the user re-evaluate/re-init the plugin when he needs to.
Let's say that in the first example the HTML is changed dynamically after the page is loaded, becoming:
<span data-color="purple"></span>
With the first fiddle there's no way to re-init the plugin, so I am now testing some solutions.
Possible solutions
Exposing a global
The most obvious is exposing a global. If we go this route the fiddle becomes
http://jsfiddle.net/gleezer/089om9z5/4/
where the only real difference is removing the selection of the element, leaving it to the user:
// we remove this line
// var elem = document.querySelectorAll('[data-color]')[0];
and adding something like (again, i am simplifying for the sake of the question):
window.changeColor = changeColor;
to the above code in order to expose the init method to be called from anywhere.
Although this works I am not satisfied with it. I am really looking for an alternative solution, as I don't want to lose the ease of use of the original approach and I don't want to force anyone using the script adding a new global to their projects.
Events
One solution I have found is leveraging events. By putting something like this in the plugin body:
elem.addEventListener('init', function() {
changeColor.init(elem);
}, false);
anybody will be able to just create an event an fire it accordingly. An example in this case:
var event = new CustomEvent('init', {});
span.dispatchEvent(event);
This would re-init the plugin whenever needed. A working fiddle is to be found here:
http://jsfiddle.net/gleezer/tgztjdzL/1/
The question (finally)
My question is: is there a cleaner/better way of handling this?
How can i let people using this plugin without the need of a global or having to initialise the script themselves the first time? Is event the best way or am I missing some more obvious/better solutions?
You can override Element.setAttribute to trigger your plugin:
var oldSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
oldSetAttribute.call(this, name, value);
if (name === 'data-color') {
changeColor.init(this);
}
}
Pros:
User does not have to explicitly re-initialize the plugin. It will happen automatically when required.
Cons:
This will, of course, only work if the user changes data-color attributes using setAttribute, and not if they create new DOM elements using innerHTML or via some other approach.
Modifying host object prototypes is considered bad practice by many, and for good reasons. Use at your own risk.

Difference between creating Jquery plugins with $fn and without it

I'm looking on some Jquery plugins,and I want to build my own. In all tutorials I see syntax like this:
(function($) {
$.fn.myPlugIN = function() {
// Add plugin code here
};
})(jQuery);
But when I look at the code of few popular plugins like iScroll.js, Carousel,js
I see the dont use the $.fn syntax, they write it inside one function
(function(){
function iScroll (el, options) {
var that = this, i;
//some code
}
})();
What are the difference between those function and the use of them?
That's the difference between a jQuery plugin and a basic javascript plugin (or module).
iScroll doesn't use jQuery, it has no reason to be defined as a jQuery plugin. Like most javascript plugins not based on a specific framework, it's based on the very convenient module pattern which lets you define many private variables not cluttering the global namespace. The snippet you show doesn't contain the part where a unique variable is exported in the global namespace.
See source :
if (typeof exports !== 'undefined') exports.iScroll = iScroll;
else window.iScroll = iScroll;
iScroll (and I'm assuming Carousel as well) are not jQuery plugins, so their code-behind syntax will be a bit different.
A jQuery plugin is an extension function that works off jQuery objects. For example, if I create a foo jQuery plugin, I should be able to use that via something like $('#some-element').foo();.
To be able to work this way, jQuery plugins extend the $ prototype by adding functions to the $.fn variable like so:
$.fn.foo = function () {
// foo!
};
iScroll, on the other hand, is not a jQuery plugin. It doesn't work off a jQuery object, so you don't expect it to work like $('#some-element').iScroll();. You use it by invoking iScroll() itself.
var obj = new iScroll('container');
As you'd expect, the difference in means of use will dictate different ways of declaring the plugin in its script file.

How to use dojo/behavior on dojo widget?

I read this article (Using dojo.behavior), and want to use the behavior module in my project as event handling module.
But I have a problem that, for DOM nodes, it works wonderful, but how can I use it on the Dojo widgets?
If I use dojo/on module, I can do it like this:
var buttonNode = dijit.byId("myButton");
on(buttonNode, "onClick", buttonHandler);
or
dijit.byId("myButton").onClick = buttonHandler;
But, if I use behavior module,
behavior.add({
"#myButton": {
onClick: buttonHandler
}
});
it doesn't work. (Of course I called behavior.apply() after I finished page render.)
The code below doesn't work either.
behavior.add({
"[widgetid='myButton']": {
onClick: buttonHandler
}
});
After some investigation, I found the reason the code above not work is because a button widget is composed by many s and an inner . And if I use the id specified by data-dojo-id, it will point to a instead of the that I hope the event bind with.
I found a solution which can walk out this situation,
behavior.add({
"[widgetid='myButton'] input": {
onclick: buttonHandler
}
}
but the css selector is too complex and it depends on what type the widget is.
Is there a good solution to apply dojo/behavior on widgets just like on dom nodes?
It looks like what you really what is to hook up an event on the widget object, but behavior is designed to access the DOM instead. So, I think you're stuck with your workaround.

Is there a way to trigger an event when inserting html?

It's for a backbone application,
I'm using Jquery html() function to insert my views templates into the layout everywhere, and I would like to be able to trigger an event each time the html() function of jQuery is called to check the html of the page.
Is there a way to do that ?
( Like App.on('html', blablabla...); )
Thank you !
As Marc B suggested DOM MutationEvents is available on some browsers (not many). By default jQuery does not fire any event when using html, but you can define your own behaviour for this, for example:
(function($) {
var html_ref = $.fn.html;
$.fn.extend({
html : function() {
$(document).trigger( 'html_change' );
return html_ref.apply(this, arguments);
}
});
})($);
It should work, didn't test it though. You can use the same with .text method. Now you can simply use:
$(document).bind( 'html_change', function() {
// Hurray! Html changed!
});
That's the idea, use it as you wish.
AFAIK, the jQuery html() method doesn't fire any subscribable events per se, but you could probably roll your own implementation of a simple Observer pattern. I use this across a large number of projects and it provides a great, clean, lightweight way to encapsulate arbitrary event handling across loosely-coupled modules.
However, this is presuming that you have programmatic control over every time the html() method is called - if not, then this would be more difficult, as there is no callback function to hook into.

Categories