I have this code to set "State Machine" in view of a javascript application:
var Events = {
bind: function(){
if ( !this.o ) this.o = $({});
this.o.bind(arguments[0], arguments[1])
},
trigger: function(){
if ( !this.o ) this.o = $({});
this.o.trigger(arguments[0], arguments[1])
}
};
var StateMachine = function(){};
StateMachine.fn = StateMachine.prototype;
$.extend(StateMachine.fn, Events);
StateMachine.fn.add = function(controller){
this.bind("change", function(e, current){
console.log(current);
if (controller == current)
controller.activate();
else
controller.deactivate();
});
controller.active = $.proxy(function(){
this.trigger("change", controller);
}, this);
};
var con1 = {
activate: function(){
console.log("controller 1 activated");
},
deactivate: function(){
console.log("controller 1 deactivated");
}
};
var sm = new StateMachine;
sm.add(con1);
con1.active();
What I don't understand at this point is where the current parameter in bind function comes from (That is: this.bind("change", function(e, current){...}). I try to log it on firebug console panel and it seems to be the controller parameter in StateMachine.fn.add function. Could you tell me where this parameter comes from?
Thank you.
As far as I understand, you specified the second argument to be passed to you event callback here:
this.trigger("change", controller);
jQuery's trigger method will call all binded functions, passing the Event object as the first argument (always), and then, after it, all the arguments you passed to .trigger() method after the name of event.
Related
trying to get my head around objects, methods, closures, etc... in Javascript.
Can't see why this isn't working, some fundamental flaw in my thinking I guess. I'm expecting the val variable to be passed through to the addNote() function but it isn't. I thought that any variables declared outside of a function are available to that function, as long as they're not within another function. Is that not correct?
if(typeof(Storage) !== "undefined") {
console.log(localStorage);
var $input = $('#input'),
$submit = $('#submit'),
$list = $('#list'),
val = $input.val();
var noteApp = {
addNote : function(val) {
var item = val.wrap('<li />');
item.appendTo($list);
clearField();
},
clearField : function() {
$input.val = '';
},
delNote : function(note) {
}
};
$submit.on('click', function(){
noteApp.addNote();
});
} else {
}
I'm trying to learn how the pros manage to get their code so clean, concise and modular. I figured a note app would be a perfect start, shame I got stuck at the first hurdle...
Cheers.
There are several issues with the code in the question
defining an argument named val and not passing an argument to the function
when calling clearField() inside the object literal it's this.clearField()
You're only getting the value once, not on every click
val is a string, it has no wrap method
$input.val = ''; is not valid jQuery
I would clean it up like this
var noteApp = {
init: function() {
if (this.hasStorage) {
this.elements().events();
}
},
elements: function() {
this.input = $('#input');
this.submit = $('#submit');
this.list = $('#list');
return this;
},
events: function() {
var self = this;
this.submit.on('click', function(){
self.addNote();
});
},
hasStorage: (function() {
return typeof(Storage) !== "undefined";
})(),
addNote: function() {
this.list.append('<li>' + this.input.val() + '</li>');
this.clearField();
return this;
},
clearField: function() {
this.input.val('');
},
delNote : function(note) {
}
}
FIDDLE
Remember to call the init method
$(function() { noteApp.init(); });
In your call to addNote(), you don't pass any argument for the val, so it will be undefined:
noteApp.addNote();
// ^^ nothing
Pass the input (seems you want the jQuery object not the string value because of your val.wrap call):
noteApp.addNote($input);
When you declare the val in the function, it is scoped to that function and will only be populated if the function call passes a value for that argument. Even if you have another variable in an upper scope with the same name val, they are still differentiated. Any reference to val in the function will refer to the local val not the upper scope.
I'm using mootools and working on popup menu:
document.getElement('.cart a').toggle(
function() {
this.getParent('div').removeClass('open');
this.getNext('.cart_contents').hide();
},
function() {
this.getParent('div').addClass('open');
this.getNext('.cart_contents').show();
})
);
The toggle function implementation:
Element.implement({
toggle: function(fn1,fn2){
this.store('toggled',false);
return this.addEvent('click',function(event){
event.stop();
if(this.retrieve('toggled')){
fn1.call(this);
}else{
fn2.call(this);
}
this.store('toggled',!(this.retrieve('toggled')));
});
}
});
The outer click function:
Element.Events.outerClick = {
base : 'click',
condition : function(event){
event.stopPropagation();
return false;
},
onAdd : function(fn){
this.getDocument().addEvent('click', fn);
},
onRemove : function(fn){
this.getDocument().removeEvent('click', fn);
}
};
I would like to add outerclick event to close my popup menu:
document.getElement('.cart a').toggle(
function() {
this.getParent('div').removeClass('open');
this.getNext('.cart_contents').hide();
},
function() {
this.getParent('div').addClass('open');
this.getNext('.cart_contents').show();
}).addEvent('outerClick',function() {
// Error: Wrong code below
// Uncaught TypeError: Object #<HTMLDocument> has no method 'getParent'
this.getParent('div').removeClass('open');
this.getNext('.cart_contents').hide();
});
Error: Uncaught TypeError: Object # has no method 'getParent'
Thanks.
This is a problem (or a deficiency) to do with the outerClick implementation by Darren. It's not a bug - it's built to work as fast as possible. you just need to understand what it does when it binds the actual event to document.
Element.Events.outerClick = {
base : 'click',
condition : function(event){
event.stopPropagation();
return false;
},
onAdd : function(fn){
// the event actually gets added to document!
// hence scope in fn will be document as delegator.
this.getDocument().addEvent('click', fn);
},
onRemove : function(fn){
this.getDocument().removeEvent('click', fn);
}
};
So the functions will run with context this === document.
One way to fix it is to bind the callback specifically to the element. Problem is, removing it won't work as .bind will return a unique new function that won't match the same function again.
(function(){
var Element = this.Element,
Elements = this.Elements;
[Element, Elements].invoke('implement', {
toggle: function(){
var args = Array.prototype.slice.call(arguments),
count = args.length-1,
// start at 0
index = 0;
return this.addEvent('click', function(){
var fn = args[index];
typeof fn === 'function' && fn.apply(this, arguments);
// loop args.
index = count > index ? index+1 : 0;
});
}
});
Element.Events.outerClick = {
base : 'click',
condition : function(event){
event.stopPropagation();
return false;
},
onAdd : function(fn){
this.getDocument().addEvent('click', fn.bind(this));
},
onRemove : function(fn){
// WARNING: fn.bind(this) !== fn.bind(this) so the following
// will not work. you need to keep track of bound fns or
// do it upstream before you add the event.
this.getDocument().removeEvent('click', fn.bind(this));
}
};
}());
document.id('myp').toggle(
function(e){
console.log(e); // event.
this.set('html', 'new text');
},
function(){
console.log(this); // element
this.set('html', 'old text');
},
function(){
this.set("html", "function 3!");
}
).addEvent('outerClick', function(e){
console.log(this, e);
});
http://jsfiddle.net/dimitar/UZRx5/ - this will work for now - depends if you have a destructor that removes it.
Another approach is when you add the event to do so:
var el = document.getElement('.cart a');
el.addEvent('outerClick', function(){ el.getParent(); // etc });
// or bind it
el.addEvent('outerClick', function(){ this.getParent(); }.bind(el));
it can then be removes if you save a ref
var el = document.getElement('.cart a'),
bound = function(){
this.getParent('div');
}.bind(el);
el.addEvent('outerClick', bound);
// later
el.removeEvent('outerClick', bound);
that's about it. an alternative outerClick is here: https://github.com/yearofmoo/MooTools-Event.outerClick/blob/master/Source/Event.outerClick.js - not tried it but looks like it tries to do the right thing by changing scope to element and keeping a reference of the memoized functions - though multiple events will likely cause an issue, needs event ids to identify the precise function to remove. Also, it looks quite heavy - considering the event is on document, you want to NOT do too much logic on each click that bubbles to that.
I've just started with Rivets.js, which looks promising as simple data-binding framework.
I've arrived at the point that I don't know how to pass "custom arguments" to the rv-on-click binder, so I tried to take the idea from this: https://github.com/mikeric/rivets/pull/34
My code:
rivets.binders["on-click-args"] = {
bind: function(el) {
model = this.model;
keypath = this.keypath;
if(model && keypath)
{
var args = keypath.split(' ');
var modelFunction = args.shift();
args.splice(0, 0, model);
var fn = model[modelFunction];
if(typeof(fn) == "function")
{
this.callback = function(e) {
//copy by value
var params = args.slice();
params.splice(0, 0, e);
fn.apply(model, params);
}
$(el).on('click', this.callback);
}
}
},
unbind: function(el) {
$(el).off('click', this.callback);
},
routine: function(el, value) {
}
}
This code is working, my question is: is this the correct way?
If you want to pass a custom argument to the event handler then this code might be simpler:
rivets.configure({
// extracted from: https://github.com/mikeric/rivets/issues/258#issuecomment-52489864
// This configuration allows for on- handlers to receive arguments
// so that you can onclick="steps.go" data-on-click="share"
handler: function (target, event, binding) {
var eventType = binding.args[0];
var arg = target.getAttribute('data-on-' + eventType);
if (arg) {
this.call(binding.model, arg);
} else {
// that's rivets' default behavior afaik
this.call(binding.model, event, binding);
}
}
});
With this configuration enabled, the first and only argument sent to the rv-on-click handler is the value specified by data-on-click.
<a rv-on-click="steps.go" data-on-click="homePage">home</a>
This is not my code (I found it here), but it does work with Rivets 0.8.1.
There is also a way to pass the current context to the event handler. Basically, when an event fires, the first argument passed to the handler is the event itself (click, etc), and the second argument is the model context.
So lets say that you are dealing with a model object that is the product of a rv-each loop...
<div rv-each-group="menu.groups">
<input rv-input="group.name"><button rv-on-click="vm.addItem">Add item</button>
___ more code here ___
</div>
Then you can access the current "group" object in the event handler like this:
var viewModel = {
addItem: function(ev, view) {
var group = view.group;
}
};
More details on this technique can he found here https://github.com/mikeric/rivets/pull/162
I hope this helps.
There is another answer here:
https://github.com/mikeric/rivets/issues/682
by Namek:
You could define args formatter:
rivets.formatters.args = function(fn) {
let args = Array.prototype.slice.call(arguments, 1)
return () => fn.apply(null, args)
}
and then:
rv-on-click="on_click | args 1"
Please note that args is not a special id, you can define anything instead of args.
To pass multiple arguments use space: "on_click | args 1 2 3 'str'"
I am writing a jQuery event plugin and I need to pass some data to a argument. So if I use it like this:
$(element).on('myevent', function(event, myargument) { console.log(myargument); });
I wan't to get the myargument object and it's properties (e.g. name), which is set in the handler.
So how would this work with the code below?
SmartScroll.myevent = {
setup: function() {
var myargumentData,
handler = function(evt) {
var _self = this,
_args = arguments;
// The data I wan't to get
myargumentData = {
name: "Hello"
};
evt.type = 'myevent';
jQuery.event.handle.apply(_self, _args);
};
jQuery(this).bind('scroll', handler).data(myargumentData, handler);
},
teardown: function() {
jQuery(this).unbind('scroll', jQuery(this).data(myargumentData));
}
};
You can modify an object passed to add.
$.event.special.foo = { add: function(h) {
var hh = h.handler;
h.handler = function(e, a) {
hh.call(this, e, a * 2);
};
} };
Now, let's bind the event:
$("body").on("foo", function(e, a) {
console.log(a);
});
Fire the event and see what happens:
$("body").trigger("foo", [ 10 ]); // 20
on: function( types, selector, data, fn, /*INTERNAL*/ one )
According to the source.
so if i understand your problem , just do :
$(element).on('myevent',{my_data:"foo"},function(event) {
console.log(event.data.my_data);
});
prints "foo"
I know I could do this with closures (var self = this) if object was a function:
click here
<script type="text/javascript">
var object = {
y : 1,
handle_click : function (e) {
alert('handling click');
//want to access y here
return false;
},
load : function () {
document.getElementById('x').onclick = this.handle_click;
}
};
object.load();
</script>
The simplest way to bind the call to handle_click to the object it is defined in would be something like this:
var self=this;
document.getElementById('x').onclick =
function(e) { return self.handle_click(e) };
If you need to pass in parameters or want to make the code look cleaner (for instance, if you're setting up a lot of similar event handlers), you could use a currying technique to achieve the same:
bind : function(fn)
{
var self = this;
// copy arguments into local array
var args = Array.prototype.slice.call(arguments, 0);
// returned function replaces first argument with event arg,
// calls fn with composite arguments
return function(e) { args[0] = e; return fn.apply(self, args); };
},
...
document.getElementById('x').onclick = this.bind(this.handle_click,
"this parameter is passed to handle_click()",
"as is this one");
So, the event handler part wires up just fine (I tested it myself) but, as your comment indicates, you have no access to the "y" property of the object you just defined.
This works:
var object = {
y : 1,
handle_click : function (e) {
alert('handling click');
//want to access y here
alert(this.y);
return false;
},
load : function () {
var that = this;
document.getElementById('x').onclick = function(e) {
that.handle_click(e); // pass-through the event object
};
}
};
object.load();
There are other ways of doing this too, but this works.
I see how to do it with Jason's latest one. Any way to do it without the anonymous function?
We can directly pass an object with a handler method thanks to AddEventListener, and you will have access to its attributes:
http://www.thecssninja.com/javascript/handleevent
Hope this will help those who, like me, will look for this topic some years after!