I started reimplementing some js code with knockout.js.
I have a singleton with some functions in it:
Dps = {
someFunction: function() {
this.anotherFunction();
},
anotherFunction: function() {
console.log('tehee');
}
}
Now there are also some bindings which calls functions of this singleton:
<input type="text" data-bind="event: { change: Dps.someFunction }" />
The annoying thing is, that the context in the called function is the event, so I can't call this.anotherFunction()
Is there a nice way to get rid of this?
PS: I'm aware that I could do something like Dps.someFunction() instead, but this is not nice in my opinion.
data-bind="event: { change: Dps.someFunction.bind(Dps) }"
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Your functions behaves as "static"
So either you have to do Dps.anotherFunction but you don't want that, but I don't see why tbh.
You can also call ko.applyBindings(Dps) and then your code would work fine. However I guess that's not what you're looking for either. Probably you have another viewmodel all together, no?
Another solution is to make Dps into a function which you instantiate
On jsfiddle: http://jsfiddle.net/PrbqZ/
<input type="text" data-bind="event: { change: myDps.someFunction }" />
var Dps = function() {
var self = this;
this.someFunction = function() {
self.anotherFunction();
};
this.anotherFunction = function() {
console.log('tehee');
};
}
var myDps = new Dps();
//normally use dom ready, just simulating here
setTimeout(function(){
ko.applyBindings();
}, 500)
Related
I have an Object and I want to bind a function to a button when Object was initialized.
var MyClass = {
Click: function() {
var a = MyClass; // <---------------------- look here
a.Start();
},
Bind: function() {
var a = document.getElementById('MyButton');
a.addEventListener('click', this.Click, false); //<---------- and here
},
Init: function() {
this.Bind();
}
}
So, I'm new at using it and I don't know if object can be declared like this (inside Click() function that should be done after clicking a button):
Is it a bad practise? Which could be the best way in this case when adding an event here?
Edit: fiddle
Firstly you have a syntax error. getElementsById() should be getElementById() - no s. Once you fix that, what you have will work, however note that it's not really a class but an object.
If you did want to create this as a class to maintain scope of the contained methods and variables, and also create new instances, you can do it like this:
var MyClass = function() {
var _this = this;
_this.click = function() {
_this.start();
};
_this.start = function() {
console.log('Start...');
}
_this.bind = function() {
var a = document.getElementById('MyButton');
a.addEventListener('click', this.click, false);
};
_this.init = function() {
_this.bind();
};
return _this;
}
new MyClass().init();
<button id="MyButton">Click me</button>
For event listeners it's easiest and best to use jQuery, for example if you want to have some .js code executed when user clicks on a button, you could use:
https://api.jquery.com/click/
I don't know how new you are to .js, but you should look up to codecademy tutorials on JavaScript and jQuery.
.click() demo:
https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_event_click
I'm building a custom wizard in knockout that dynamically loads knockout components during each "step" of the wizard. I've managed to get all of this working without much hassle, and it seems to work pretty well.
However, I'm wanting to implement some callbacks within the wizard when certain events happen. For example, before and after navigation.
Currently, one of my navigation functions looks like this:
this.goNext = function () {
if (!this.canGoNext()) return;
this.currentStep(this.pages()[this.currentIndex() + 1]);
this.loadPage();
}
I would like to build 2 callback functions called beforePageChange and onPageChange.
My general assumption is that beforePageChange would pass in a couple parameters, notably the current page and the next page. However, I also want it to be able to be observed from any other class utilization the wizard.
For example, on my parent page I would have something like:
this.wizard = Site.loadWizard(arguments);
this.wizard.beforePageChange(function(options) {
if (!options.currentPage.complete) return false;
// do stuff
return true;
});
In turn the wizard would execute its navigation commands and trigger the appropriate callbacks.
I feel like there's something I'm just fundamentally missing here.
Edit
My current version works as follows:
In the wizard:
this.canChangePage = ko.obserable(true);
this.beforePageChange = function (options) {
};
this.beforePageChangeHandler = function (options) {
this.beforePageChange(options);
// do stuff
return true;
};
this.onPageChange = function (options) {
};
this.onPageChangeHandler = function (options) {
this.onPageChange(options);
//do stuff
return true;
}
On the page implementing the wizard:
this.wizard = Site.loadComponent(params, function () {
this.wizard.beforePageChange = function (options) {
this.canChangePage(false);
};
}.bind(this));
I'm not sure if there's a better way to implement this, or if this is the best solution.
The solution Tomalak described in their comment (I think):
Since you already have access to the wizard instance, you can subscribe to its currentStep observable. To get notifications before it changes, you pass a third parameter: "beforeChange". (The second is the this context).
var Wizard = function() {
this.currentStep = ko.observable(0);
}
Wizard.prototype.next = function() {
this.currentStep(this.currentStep() + 1);
}
Wizard.prototype.prev = function() {
this.currentStep(this.currentStep() - 1);
}
var Page = function() {
this.wizard = new Wizard();
this.wizard.currentStep.subscribe(function(oldStep) {
console.log("Page work before step change from step", oldStep);
}, this, "beforeChange");
this.wizard.currentStep.subscribe(function(newStep) {
console.log("Page work after step change to", newStep);
});
}
ko.applyBindings(new Page());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with:wizard">
<button data-bind="click: prev">back</button>
<span data-bind="text: currentStep"></span>
<button data-bind="click: next">next</button>
</div>
I got the following binding working like a charm :
<button class="flatButton buttonPin" data-bind="click:EnterPinMode">Add pin</button>
In my viewmodel I define the Handler like this :
self.EnterPinMode = function(data,event)
{
//Doing several things here
//....
}
Now, let's say I want to change the behavior of that button after the first click on it...how could I do it ? I already managed quite easily to change the button text :
self.EnterPinMode = function(data,event)
{
//Doing several things here
//....
var curButton = $(event.target);
curButton.text("Cancel");
}
But what about changing the button behaviour ? If I had set this handler through jQuery, that wouldn't be an issue, but is there a way to "replace" the click binding on that control so that now it will call ExitPinMode handler, for example.
I've got some doubts on this being possible given the fact that knockout works only with declarative binding (at least without plugin...), but I thought it was worth asking.
Please note that I will actually need some kind of 3 ways toggle, I just simplify it here to a "normal" toggle for the sake of the example.
I think using a hasBeenClicked flag that's private to the view model is fine, and probably the best solution for this.
If you really want to swap out the handler, that should be easy enough, though, with something like this:
function enterPinMode() {
//Doing several things here
//....
var curButton = $(event.target);
curButton.text("Cancel");
//set click handler to a step 2 function
self.pinAction = exitPinMode;
}
function exitPinMode() {
//....
}
self.pinAction = enterPinMode;
Maybe one of the simplest solution is to add a boolean like hasBeenClicked set to false at the begining and then set it to true.
Example:
self.hasBeenClicked = false;
self.EnterPinMode = function(data,event)
{
if (!self.hasBeenClicked )
{
var curButton = $(event.target);
curButton.text("Cancel");
self.hasBeenClicked = true;
}
else
{
//behaviour an a second click
}
}
Hope it helps !
You can try this
var vm = function () {
var self = this;
var nextState = 'firstState';
var states = {
firstState: function () {
nextState = 'secondState';
//Do stuff
},
secondState: function () {
nextState = 'thirdState';
//Do stuff
},
thirdState: function () {
nextState = 'firstState';
//Do stuff
}
}
self.EnterPinMode = function () {
states[nextState].call();
}
}
What you should try to remember first about MVVM is that you are designing an object to represent your view. If your view will have different states, There is nothing wrong with having your viewmodel know about these states and knowing what to do in what state. Stick with MVVM. You wont be disappointed.
The 2nd answer to this question nicely explains how event declarations in Backbone.js views are scoped to the view's el element.
It seems like a reasonable use case to want to bind an event to an element outside the scope of el, e.g. a button on a different part of the page.
What is the best way of achieving this?
there is not really a reason you would want to bind to an element outside the view,
there are other methods for that.
that element is most likely in it's own view, (if not, think about giving it a view!)
since it is in it's own view, why don't you just do the binding there, and in the callback Function,
use .trigger(); to trigger an event.
subscribe to that event in your current view, and fire the right code when the event is triggered.
take a look at this example in JSFiddle, http://jsfiddle.net/xsvUJ/2/
this is the code used:
var app = {views: {}};
app.user = Backbone.Model.extend({
defaults: { name: 'Sander' },
promptName: function(){
var newname = prompt("Please may i have your name?:");
this.set({name: newname});
}
});
app.views.user = Backbone.View.extend({
el: '#user',
initialize: function(){
_.bindAll(this, "render", "myEventCatcher", "updateName");
this.model.bind("myEvent", this.myEventCatcher);
this.model.bind("change:name", this.updateName);
this.el = $(this.el);
},
render: function () {
$('h1',this.el).html('Welcome,<span class="name"> </span>');
return this;
},
updateName: function() {
var newname = this.model.get('name');
console.log(this.el, newname);
$('span.name', this.el).text(newname);
},
myEventCatcher: function(e) {
// event is caught, now do something... lets ask the user for it's name and add it in the view...
var color = this.el.hasClass('eventHappened') ? 'black' : 'red';
alert('directly subscribed to a custom event ... changing background color to ' + color);
this.el.toggleClass('eventHappened');
}
});
app.views.sidebar = Backbone.View.extend({
el: '#sidebar',
events: {
"click #fireEvent" : "myClickHandler"
},
initialize: function(){
_.bindAll(this, "myClickHandler");
},
myClickHandler: function(e) {
window.user.trigger("myEvent");
window.user.promptName();
}
});
$(function(){
window.user = new app.user({name: "sander houttekier"});
var userView = new app.views.user({model: window.user}).render();
var sidebarView = new app.views.sidebar({});
});
Update: This answer is no longer valid/right. Please see other answers below!
Why do you want to do this?
Apart from that, you could always just bind it using regular jQuery handlers. E.g.
$("#outside-element").click(this.myViewFunction);
IIRC, Backbone.js just uses the regular jQuery handlers, so you're essentially doing the same thing, but breaking the scope :)
I've got following JavaScript functions but want to refactor the $(document).ready() as I've got 2 instance of it. How can I achieve this?
FlashMessenger = {
init: function() {
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
}
}
SelectLanguage = {
init: function() {
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
}
}
$(document).ready(FlashMessenger.init);
$(document).ready(SelectLanguage.init);
It’s perfectly acceptable to set multiple handlers for $(document).ready, although you may have a good reason to do otherwise that I’m not aware of. You might be interested in knowing that $(handler) can be used as shorthand for $(document).ready(handler):
$(FlashMessenger.init);
$(SelectLanguage.init);
If you really want them in one call though, try this:
$(function() {
FlashMessenger.init();
SelectLanguage.init();
});
First off, there's no reason you have to combine them.
But if you want to:
$(document).ready(function(jq){
FlashMessenger.init(jq);
SelectLanguage.init(jq);
});
Breaking it down:
Create a function to do all your init (it can be named or anonymous; the one above is anonymous).
Have it call the other init functions, passing in the jQuery instance that jQuery passes you just in case they use it.
You might choose to wrap each init call in a try/catch block as well, so that errors in one init don't prevent the next init from occuring, but that depends on your needs.
Just combine them into one call with an anonymous function:
$(document).ready(function()
{
FlashMessenger.init();
SelectLanguage.init();
});
$(document).ready(function() {
FlashMessenger.init();
SelectLanguage.init();
});
Option 1
FlashMessenger = {
init: function() {
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
}
}
SelectLanguage = {
init: function() {
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
}
}
$(function(){
FlashMessenger.init();
SelectLanguage.init();
});
Option 2
FlashMessenger = {
init: function() {
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
}
}
SelectLanguage = {
init: function() {
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
}
}
$(document).ready(function(){
FlashMessenger.init();
SelectLanguage.init();
});
Option 3
You actually don't need those 2 objects since the only hold the init methods, so here's the ultimate solution, in my opinion, unless you use those objects elsewhere.
$(function(){
$('#selectLanguageId').change(function() {
$('#frmSelectLanguage').submit();
});
setTimeout(function() {
$(".flash").fadeOut("slow", function () {
$(".flash").remove();
});
}, 5000);
})
I prefer 2 and 3 for this reason.
I think what the op is saying is, "If in the future I have a third function to be invoked at document.ready, then how do I do it without touching that piece of code?"
If you do not want multiple $(document).ready() calls, you could just create an array called startupHooks and add each method to it:
startupHooks[ startupHooks.length ] = myNewStartupHook;
and your startup script could look like
$(document).ready(function() {
for( var i=0; i<startupHooks.length; i++ ) {
startupHooks[i]();
}
}
I know that is not mighty useful, but if that appeals to you, you can do it this way.
Personally, I'd go with multiple $(document).ready() calls.
Personally I'd go for not using document.ready at all.
If you place the scripts at the end of your html-page(just before the tag) you can just write in any way you like.
Maybe this doesn't work for 0.01% of the scripts but it never failed to work for me.
Positive effect of this is that the initial HTML+CSS rendering goes faster.
You can also read about it on yahoo. http://developer.yahoo.com/performance/rules.html#js_bottom