How to use dojo/behavior on dojo widget? - javascript

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.

Related

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.

Defining and triggering custom events on custom module in YUI3

This is how we can create a custom module in YUI3,
<script type="text/javascript">
YUI.add('my-module', function (Y) {
// Write your module code here, and make your module available on the Y
// object if desired.
Y.MyModule = {
sayHello: function () {
console.log('Hello!');
}
};
});
</script>
But now I would like to, on this module, define some custom events and later trigger them, I just couldn't find any information about this on the YUI3 official website.
How can we actually do this?
Custom events are actually pretty important throughout YUI. This documentation page describes them in detail: http://yuilibrary.com/yui/docs/event-custom/. Read this page and some of the examples in the sidebar.
The easiest and simplest way to fire a custom event is to fire it from the Y, as in Y.fire("myEvent"). However, if you want to fire an event from your object, you would need to give your object the EventTarget API and call this.fire("myEvent"). Most people do this by extending Y.Base, which includes Y.EventTarget. See http://yuilibrary.com/yui/docs/base/ -- if you extend Base, you get a fire() method, the ability to listen for events with on() or after(), plus lots of other goodies.

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.

dojo how to override dijit class method

I need to override onClick method in the Tree.js. Is there any common way to override dojo/dijit classes methods?
I'm a bit confused, since you were already doing this in the last question you posted.
You've got a few choices, depending on what you want to do.
Clobbering method stubs
In the case of true stubs like onClick, it's potentially as easy as clobbering that method on your widget instance.
Programmatically:
var myWidget = new dijit.Tree({
...,
onClick: function(item, node, evt) {
/* handler code here */
}
};
Or declaratively (this is exactly the same as what you were doing in your last question):
<div dojoType="dijit.Tree" ...>
<script type="dojo/method" event="onClick" args="item,node,evt">
/* handler code here */
</script>
</div>
Connecting to method invocations
In other cases, perhaps you need to do something whenever a given method gets called, in which case you could use the widget's connect method (which is a nicer version of dojo.connect that will automatically clear itself when the widget is destroyed). In that case you could do something like this:
Programmatically:
//execute the given function whenever myWidget's onClick method is called
myWidget.connect(myWidget, 'onClick', function(item, node, evt) {
/* handler code here */
});
Declaratively, this can be done very similarly to the above, except instead of type="dojo/method", make sure you use type="dojo/connect"
<div dojoType="dijit.Tree" ...>
<script type="dojo/connect" event="onClick" args="item,node,evt">
/* handler code here */
</script>
</div>
Note that when you connect like this, your code will execute after the method whose invocation you are connecting to. If you need to do something before, your best option is probably to dojo.declare your own extension to the widget. If you need to go that far, I'll elaborate, but I think you'll likely be set with one of the above options.
Edit #1: Connecting the dots (no pun intended...oh heck, yes it was)
Since it seems that my comment appended to my answer to the original question was somehow not clear enough, here's a code block modifying the original code in that question based on the simple two steps exactly as I explained in that comment. The only tiny wrinkle is that the arguments passed to _onClick are slightly different.
<div dojoType="dijit.Tree" ...>
<script type="dojo/connect" event="_onClick" args="node,evt">
/* handler code here. In this case, item is accessible via node.item */
</script>
</div>
This solution may feel a bit sub-optimal since it involves connecting to a method that's suggested to be private. However, it's a way that should work regardless of whether openOnClick is true or not. If you are certain you're going to have openOnClick set to true, you could potentially write a single function, then connect it to both onClick and onOpen instead (both get passed the item, then the node).
Edit #2: Common functions, connecting programmatically
To answer your follow-up questions, I'd like to actually address them in reverse order - since if you are interested in connecting programmatically, it will actually make the other question easier to answer.
So first, to answer your connect question: you definitely don't want to be using dojo.byId, as that's not giving you the Tree widget, that's giving you some DOM node (probably the topmost) associated with the widget. As a general rule, dojo methods don't know anything about dijit stuff.
What you do want to be doing, is what I suggested above. Here it is applied as per the code you attempted. Also note that onClick has a capital C - another general rule: widget events use camel case notation, as a way to distinguish them from plain DOM events which don't.
var mytree = dijit.byId("mytree");
mytree.connect(mytree, "onClick", function(item) {
/* ... */
});
Now, to take that a step further and resolve your other inquiry, if you want to bind some common functionality to onClick and onOpen and onClose, you could define a function first, then connect it to both. This is one of the many things that makes JavaScript awesome - the availability of functions as first-class objects that can be easily passed around.
function handleClick(item) {
/* do stuff here.
Inside this function you can assume 'this' is the tree,
since connect will ensure it runs in-context.
*/
}
var mytree = dijit.byId("mytree");
mytree.connect(mytree, "onClick", handleClick);
mytree.connect(mytree, "onOpen", handleClick);
mytree.connect(mytree, "onClose", handleClick);
Now there's one important remaining question: where should we do this? The best place is likely inside a function passed to dojo.ready (or dojo.addOnLoad, same thing, ready was added as a synonym in 1.4) so that it will run only after:
The DOM is parsed by the browser
All dojo.required modules have loaded
If you set parseOnLoad: true in djConfig, all widgets defined in the document's HTML will already be instantiated
So, sometime after your dojo.requires, in script, you'd do this:
dojo.ready(function() {
/* code from previous example goes here */
});
Give it a shot.
Also, if you're interested in a bit of reading, I've actually written about a couple of topics I touched on in this edit:
Of Dijits and DOM Nodes
dojo.required Reading
You could use:
dojo.connect(tree, 'onClick', function(item) {
/** Your Action **/
});

remove plugins in ext javascript form via code?

i have the following form item
{
fieldLabel:'Username'
,id:"username"
,name:'username'
,allowBlank:false
,plugins:[Ext.ux.plugins.RemoteValidator]
,rvOptions: {
url:'/registration/member/valid-username'
}
is it possible to remove plugins later via code?
I don't think so. init in the plugin runs when the component is initialized, so "later in the code" it's too late - "the damage has been done", and the plugin has hooked into the component's events, etc. It would be cool if I were wrong.
Well, it's functionally possible to support plugin deactivation (not sure about actually removing the plugin altogether), but most plugins probably don't do so unless they have some reason to support it. You should be able to write an override to the plugin and insert code that would allow you to activate/deactivate its functionality. Depends on the specific plugin of course, but if the plugin is well-written it should be overrideable.
My general approach would be something like:
Ext.override(Ext.ux.plugins.SomePlugin, {
isActive: true,
doSomething: function(){
if(this.isActive){
// copy orig doSomething
}
}
});
Then you could simply set pluginInstance.isActive = true/false as needed. Note that this is simplistic -- your plugin might take a lot more work to override effectively. But this approach would be a good place to start.
Or you could maybe get fancy and use createInterceptor functions on the plugin to do something similar without duplicating code.

Categories