I'm trying to define a click handler in a Mootools class. My handler presumes opening a block of links, each of which should be 'equipped' with its own click handler, which should trigger a link specific action. What I mean is let's suppose I have the following HTML code:
<div id="wrapper">
open options
<div class="optionsBlock" style="display:none">
1
2
3
</div>
</div>
Then I'm trying to define a class like this in Mootools:
var myHandler = new Class({
Implements : [Events],
initialize : function(element){
this.element = document.id(element);
this.elements = this.element.getChildren('a');
this.elements.addEvents('click', function(ev){
ev.preventDefault();
//'this' as a reference to the current element in the array, which is being clicked, correct?
this.getSibling('div.optionsBlock').setStyle('display', 'block');
var parentLink = this;
this.getSibling('div.optionsBlock').getChildren('a').addEvent('click', function(e){
e.preventDefault();
//should append the text of currently clicked link into the parent link
parentLink.appendText(this.get('text'))
});
});
}
});
new myHandler('wrapper');
This is just an illustration of how I can imagine the code should be like (and I'm sure this code is not good at all), but I really need some help regarding the following:
Since adding new events constatly changes the scope of 'this', how should I keep a reference both to the class instance and the element being clicked?
How should I modify the class in order not to have the entire code inside the initialize method? I tried to create separate methods for every event handler, but as a result I got confused with the scope of 'this', with binding and trying to get all of this together really annoys me, but I want to get a grip of this knowledge.
How to keep track of the scope of 'this' when adding nested event handlers inside a class? I honestly googled and searched for an answer but for no avail.
Thanks!
scope, take your pick - asked many many times - search here for [mootools]scope this:
Mootools class variable scope
mootools variable scope
Mootools - Bind to class instance and access event object
to recap:
use a saved reference var self = this; then reference self.prop or use the fn.bind pattern
add more methods. follow single responsibility principle. eg, in your class, create attachEvents: function() {} and have initialize call that.
by using the saved reference pattern. you can fix it upriver by delegating events as opposed to creating new event callbacks on parent clicks.
Related
I have a section of my view (html) that is generated programmatically by a viewmodel/class. This uses the Aurelia DOM (Aurelia Docs - pal :: Dom) functionality to generate and add the raw HTML elements to the view.
However, I am unable to get events within the generated html to call back to the viewmodel. An example:
let deleteButton = this.dom.createElement("button");
deleteButton.setAttribute("onclick", "cancelCreditNote(`${ row.creditNoteId }`)");
A click on the generated button won't call back to the viewmodel, which does have a cancelCreditNote function. Various other things like deleteButton.setAttribute("click.delegate", "cancelCreditNote('${ row.creditNoteId }')"); do not work either.
Does anyone know how to access a viewmodel class from essentiall 'raw' html in aurelia?
Unfortunately in this instance I cannot use the standard aurelia templating to generate the HTML.
The DOM property on PAL is just an abstraction for the browser's DOM object, create element is likely just calling document.createElement which doesn't afford any Aurelia binding to the created element.
You could try using aurelia.enhance(context, element) which takes an existing DOM element and runs it through the templating engine.
With this method you can also pass a binding context to apply to the element.
In my HTML I use this:
<div id="collapsesidebar" click.delegate="toggleSidebar()">
In my view-model I have this method:
toggleSidebar(){
alert('hi');
}
You could also do this from your view-model with JQuery like this:
attached() {
$('main').on('click', ()=> alert('hi'));
}
The last option is ONLY available áfter the attached() method is triggered: before that the binding needs to do its job and only after that the elements are located inside of the dom.
In other words: this will not work:
activate(){
$('main').on('click', ()=> alert('hi'));
}
because the constructor and the activate method both get fired before the attached method.
I need to get a href value after click. My code looks like this:
$('.menu a').click (event) =>
event.preventDefault()
console.log($(this).attr('href')) # it returns 'undefined'
What am I doing wrong?
[edited]
My html code:
<div class="menu">
All
</div>
This is because of the subtle difference in Coffeescript between => and ->
In JavaScript, the this keyword is dynamically scoped to mean the
object that the current function is attached to. If you pass a
function as a callback or attach it to a different object, the
original value of this will be lost. If you're not familiar with this
behavior, this Digital Web article gives a good overview of the
quirks.
The fat arrow => can be used to both define a function, and to bind it
to the current value of this, right on the spot. This is helpful when
using callback-based libraries like Prototype or jQuery, for creating
iterator functions to pass to each, or event-handler functions to use
with bind. Functions created with the fat arrow are able to access
properties of the this where they're defined.
http://coffeescript.org/
This is why this is binded to window.
You should use:
$('.menu a').click (event) ->
event.preventDefault()
console.log($(this).attr('href')) # it returns the link !
So right now I have a table that displays some values and I have an indicator for conflicts. When the user clicks the indicator a new div appears with some animation to list all the conflicts.
Here is my HTML:
<span data-bind="if: hasConflict, click: $parent.selectProperty" class="conflictWarn"><i style="color: darkorange; cursor:pointer;" class="icon-warning-sign"></i></span>
The data might look something like this:
{
name:Property 1,
id: 1,
hasConflicts: no,
name:Property 2,
id: 2,
hasConflicts: yes,
conflicts: {
name: conflict1,
name: conflict2
}
name:Property 3,
id: 3,
hasConflicts: yes,
conflicts: {
name: conflicta,
name: conflictb
}
So the first table is going to look like this:
Property 1
Property 2 !
Property 3 !
Where ! is a conflict indicator. Clicking on the ! would display the conflicts div and also display conflict1 and conflict2 or conflicta and conflictb depending on which was clicked.
Here is the model we are working with. It's a bit complex because of the mapping for the properties from signalr. the "selectProperty" and "selectedProperty" was our way of saying which one to display conflicts for, but I'm not convinced this is the best way to do it.
function ItemViewModel() {
var self = this;
self.name = ko.observable("");
self.itemType = ko.observable("");
self.propertiesArray = ko.observableArray([]);
self.properties = ko.mapping.fromJS({});
self.selectedPropertyName = ko.observable("");
self.getItem = function (name) {
$.connection.mainHub.server.getItem(name).then(function (item) {
ko.mapping.fromJS(item.properties, self.properties);
self.propertiesArray(item.propertiesArray);
self.itemType(item.itemType.name);
self.name(item.name);
});
self.selectProperty = function (a, b) {
self.selectedPropertyName(a);
};
};
}
Originally the click event directly called a javascript function that did all the animation, but my coworker thought that might violate best practices for separating data and viewmodel in MVVM. Does it? Should we leave it calling the viewmodel function of "selectProperty" which allows us to pass context for the "conflicts popup" div? If so, do I just call the javascript function to do the animation from within the selectProperty function?
p.s. I've edited this about 800 times so I apologize if it's impossible to follow.
update I have the bindings working now, so I really just want to know what is best practice when it comes to UI animations and Knockout. Change the viewmodel from the javascript function or call the javascript function from the viewmodel function?
Regarding UI animations in my opinion it is best practice to implement custom bindings. This way code is encapsulated and it is easy to find where it is used. Check Animated transitions example on knockout website.
i'm going to extends Thomas answer with one point, custom bindings don't work when you want to animate the rendering / unrendering of the 'if' or 'with' bindings. an animation binding that tries to run at the same time as an 'if' or 'with' won't be able to complete the animation before the other binding alters the DOM, possibly removing the elements being animated from the page. there is no way to defer the binding processing until after the event completes.
for these cases animations should be implemented via the 'afterAdd' and 'beforeRemove' callbacks of the 'foreach' binding when the desire is to animate an element being added and removed from the page. 'if' and 'with' bindings can be rewritten as 'foreach' with little effort, as 'foreach' can take a single argument instead of a list. i really wish the animation tutorial would be extended to include this workaround.
Just wondering if anyone knows a good/simple approach using Durandal to disposing of or re-initializing a viewmodel once it becomes invalid?
I have a registration form that I could 're-initialize' manually after a user has completed the form and registered successfully, but I'd prefer to just dispose of it so that Durandal creates a new registraion view/view model when that particular route is accessed again.
If your viewmodel module returns a function rather than an object, it will create a new one each time rather than reusing the 'singleton' object. See the Module Values section of Creating a Module.
Updated link for the Durandal Module constructor function information: Module Values
You can split the difference:
var cache;
var ctor = function () {
if (cache) return cache;
// init logic
cache = this;
}
Just replace the if(cache) check with whatever "do I need a new thing or not" logic you like.
If you're using routing, simply redirect the user to an instance-based module (one that returns a constructor function). The user will most likely click or touch a button that signifies that he is done with the registration form. That would be the redirect action.
If you're using composition, you would still create an instance-based module. Then, you would use dynamic composition to swap it in once the user signified he was done with the registration form.
Dynamic composition is where the view and/or model attributes on a Durandal composition are, themselves, observables, referencing something like the following in the viewModel:
this.currentView = ko.observable('');
this.currentModel = ko.observable('');
Then, in your HTML:
<div>
<div data-bind="compose: {view: currentView(), model: currentModel())"></div>
</div>
When the user clicks "Done", or something to that effect, functions on your viewModel might look something like:
ctor.prototype.done = function () {
this.setCurrentView('viewmodels/registrationForm.html');
this.setCurrentModel('viewmodels/registrationForm.js');
}
ctor.prototype.setCurrentView = function (view) {
this.currentView(view);
}
ctor.prototype.setCurrentModel = function (model) {
this.currentModel(model);
}
Either one of the approaches above will create the registrationForm only when it's needed.
With Durandal 2.0, you can use the deactivate callback within the composition lifecycle. Here is some documentation http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks
Hi i have a problem in knockout 2: I want to do late binding because i am adding data-bind via jQuery
$("#button1").on ("click", function() {
lateBinding = $("#lateBindingElem);
if (lateBinding.length) {
lateBinding.attr("data-bind", "text: obs");
}
}
});
late binding is an html generated on the fly.
I have a view model created already call MyViewModel.
I want to add another attribute or another observable (could be computed or uncomputed) on the fly to existing view model? How would i do this?
Hopefully you have already found an answer elsewhere (7 months ago :D) but since I stumbled upon this question in hopes to find a solution to a similar problem, I might as well and try to give a sort-of-an-answer for anyone else looking into it. This won't let you manipulate the binding for elements you already have bound to a model but allow you to pause binding at given points and bind newly created elements to your current or a different viewmodel.
Built on Ryan Niemeyers great article about how to stop bindings and accompanying jsfiddle example is a little demo which adds new input elements to dom and binds them to different viewmodels.
Since you can only bind a section of your dom once you need to stop the downward binding at some point using a custom binding..
ko.bindingHandlers.stopBinding = {
init: function() {
return { controlsDescendantBindings: true };
}
};
assign it to a wrapper
<div data-bind="stopBinding: true" id="addNewContentHere"></div>
and insert your new elements
function addInput(){
var data=$('<input type="input" data-bind="value: newInput" />');
ko.applyBindings(MyViewModel, data[0]);
$('#addNewContentHere').append(data);
};
hope it is of some use :)