So, in my Vue instance, I have a currentTask model, which is null by default.
new Vue({
el: '...',
data: {
currentTask: null
}
});
When I click on a 'task-item', which has v-on="click: openTask" directive, i want to launch the modal with the currentTask:
methods: {
openTask: function(e) {
this.currentTask = this.clickedTask;
$('#task-modal').modal('show');
e.preventDefault();
}
}
This is working just fine, although I don't know if there is a more "magical" way to two-way bind the whole modal + visibility to the currentTask.
Now, what I need, if there is no better way to go about this, is to somehow listen for the modal close event, which normally we would do in jQuery with $('#myModal').on('hidden.bs.modal', function() {}); inside of Vue and set this.currentTask = null;.
Thank you.
You could probably use a custom directive to handle this.
Vue.directive('task-selector', {
bind: function () {
var vm = this.vm;
var el = $(this.el);
el.on('hidden.bs.modal', function() {
vm.data.currentTask = 'whatever value you want here';
});
},
update: function (newValue, oldValue) {
// i don't think you have anything here
},
unbind: function () {
// not sure that you have anything here
// maybe unbind the modal if bootstrap has that
}
})
In your html you would need to put this directive on the modal element like so...
<div id="task-modal" v-task-selector>
... modal body stuff here...
</div>
Related
With JQuery, click event of the any item in the page can be captured as below.
$(document).click(function(event){
// event.target is the clicked element object
});
How to do the same with Vue.js?
The answer provided by M U is correct and works.
Yet if you don't like messing with your template (e.g. not put a lot of event handlers in it) or your Vue app is only a small part of a bigger application, it's also perfectly fine and acceptable to register event handlers manually.
To add global event handlers in your script the Vue way you should register them in the mounted and remove them in the beforeDestroy hooks.
Short example:
var app = new Vue({
el: '#app',
mounted: function () {
// Attach event listener to the root vue element
this.$el.addEventListener('click', this.onClick)
// Or if you want to affect everything
// document.addEventListener('click', this.onClick)
},
beforeDestroy: function () {
this.$el.removeEventListener('click', this.onClick)
// document.removeEventListener('click', this.onClick)
},
methods: {
onClick: function (ev) {
console.log(ev.offsetX, ev.offsetY)
}
}
})
All of the answers provided works, but none of them mimic the real behavior of $(document).click(). They catch just clicks on the root application element, but not on the whole document. Of course you can set your root element to height: 100% or something. But in case you want to be sure, it's better to modify Bengt solution and attach event listener directly to document.
new Vue({
...
methods: {
onClick() {},
}
mounted() {
document.addEventListener('click', this.onClick);
},
beforeDestroy() {
document.removeEventListener('click', this.onClick);
},
...
});
Remember you need to use #click.stop in children elements if you for some reason need to stop event propagation to the main handler.
Create div as top node, right after <body>
Make it main container and mount VueJS on it.
<div id='yourMainDiv' #click='yourClickHandler'>
In your VueJS <script> part use it:
methods: {
yourClickHandler(event) {
// event.target is the clicked element object
}
}
Also, if you need to track click event outside of specific element, you
can use vue-clickaway component. Example from the demo:
<div id="demo">
<p v-on-clickaway="away" #click="click" :style="{ color: color }">{{ text }}</p>
</div>
new Vue({
el: '#demo',
mixins: [VueClickaway.mixin],
data: {
text: 'Click somewhere.',
color: 'black',
},
methods: {
click: function() {
this.text = 'You clicked on me!';
this.color = 'green';
},
away: function() {
this.text = 'You clicked away...';
this.color = 'red';
},
},
});
I'm writing Todo app with Backbone.js
You can see part of my code below.
Model:
var Todo = Backbone.Model.extend({
defaults : {
title: 'Task Title',
complete: false
},
initialize: function(){
this.on("change:complete", function () {
alert("foo");
});
}
});
View:
var AppView = Backbone.View.extend({
collection: todoCollection,
el: 'body',
events: {
'click #tasks li .complete-task' : 'toggleComplete'
}
toggleComplete: function (e) {
var modelCid = $(e.target).parent('li').attr('id');
if ( this.collection.get(modelCid)['complete'] ){
this.collection.get(modelCid)['complete'] = false;
} else {
this.collection.get(modelCid)['complete'] = true;
};
}
});
But something working wrong and change event in the model doesn't working. I can't understand where I have mistakes.
Help me, please.
10q.
As per the Backbone Documentation:
Set model.set(attributes, [options])
Set a hash of attributes (one or
many) on the model. If any of the attributes change the model's state,
a "change" event will be triggered on the model. Change events for
specific attributes are also triggered, and you can bind to those as
well, for example: change:title, and change:content. You may also pass
individual keys and values.
So you need to be using the set method on the model for these events to be fired. So you would need to use something like this:
this.collection.get(modelCid).set('complete',false);
I am totally new to knock-out custom binding, I am trying to integrate ckeditor with knock-out biding, I have the following binding got from Google search,
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.attr('contenteditable', true);
if (ko.isObservable(value)) {
var isSubscriberChange = false;
var isEditorChange = true;
$element.html(value());
var isEditorChange = false;
$element.on('input, change, keyup, mouseup', function () {
if (!isSubscriberChange) {
isEditorChange = true;
value($element.html());
isEditorChange = false;
}
});
value.subscribe(function (newValue) {
if (!isEditorChange) {
isSubscriberChange = true;
$element.html(newValue);
isSubscriberChange = false;
}
});
}
}
}
I have the following code to bind,
$(function () {
$.getJSON("/getdata", function (data) {
ko.applyBindings({
testList: [{
test: ko.observable()
},
{
test: ko.observable()
}]
}, document.getElementById('htmled'));
});
});
HTML as follows
<div id="htmled" data-bind="foreach:testList">
Data
<div class="editor" data-bind="wysiwyg: test">Edit this data</div>
</div>
The binding works and show the toolbar when I call the ko.applyBindings outside the $.getJSON method. But when I call applyBindings inside, the toolbars not appearing. Can any body help me on this? I must be missing something for sure, any help on this is greatly appreciated.
Jsfiddle Added
Working :http://jsfiddle.net/jogejyothish/h4Lt3/1/
Not Working : http://jsfiddle.net/jogejyothish/Se8yR/2/
Jyothish
What's happening is this:
Your page loads with the single div. KO has yet to be applied to this div.
document.ready() fires. The CKEditor script applied CKEditor to any matching divs (none).
You make your ajax call.
The Ajax call completes. You apply bindings.
KO inserts two new divs, neither of which has CKEditor.
In order to fix it, you need to add some code inside your ajax success function to manually initialise the CKEditors, like:
$(".editor").each(function(idx, el) {
CKEDITOR.inline(el)
});
Here it is, working in your fiddle:
http://jsfiddle.net/Se8yR/5/
The reason your working version works is because the bindings are applied in document.ready, so KO renders the two div elements in time, and the CKEditor is successfully applied to them.
CKEditor takes some time to load.
In your first example, it loads after ko applies, which works fine.
In the second example, it loads before ko applies. The problem is that CKEditor looks for the contenteditable attribute which you set with ko, so the editor is not created.
You can create it manually with:
CKEDITOR.inline(element).setData(valueUnwrapped || $element.html());
Doc
Demo
I am building a chat application and on my "new chats" page I have a list of contacts, which you can select one by one by tapping them (upon which I apply a CSS selected class and push the user id into an array called 'newChatters'.
I want to make this array available to a helper method so I can display a reactive list of names, with all users who have been added to the chat.
The template that I want to display the reactive list in:
<template name="newChatDetails">
<div class="contactHeader">
<h2 class="newChatHeader">{{newChatters}}</h2>
</div>
</template>
The click contactItem event triggered whenever a contact is selected:
Template.contactsLayout.events({
'click #contactItem': function (e) {
e.preventDefault();
$(e.target).toggleClass('selected');
newChatters.push(this.username);
...
The newChatters array is getting updated correctly so up to this point all is working fine. Now I need to make {{newChatters}} update reactively. Here's what I've tried but it's not right and isn't working:
Template.newChatDetails.helpers({
newChatters: function() {
return newChatters;
}
});
How and where do I use Deps.autorun() to make this work? Do I even need it, as I thought that helper methods auto update on invalidation anyway?
1) Define Tracker.Dependency in the same place where you define your object:
var newChatters = [];
var newChattersDep = new Tracker.Dependency();
2) Use depend() before you read from the object:
Template.newChatDetails.newChatters = function() {
newChattersDep.depend();
return newChatters;
};
3) Use changed() after you write:
Template.contactsLayout.events({
'click #contactItem': function(e, t) {
...
newChatters.push(...);
newChattersDep.changed();
},
});
You should use the Session object for this.
Template.contactsLayout.events({
'click #contactItem': function (e) {
//...
newChatters.push(this.username);
Session.set('newChatters', newChatters);
}
});
and then
Template.newChatDetails.helpers({
newChatters: function() {
return Session.get('newChatters');
}
});
You could use a local Meteor.Collection cursor as a reactive data source:
var NewChatters = new Meteor.Collection("null");
Template:
<template name="newChatDetails">
<ul>
{{#each newChatters}}
<li>{{username}}</li>
{{/each}}
</ul>
</template>
Event:
Template.contactsLayout.events({
'click #contactItem': function (e) {
NewChatters.insert({username: this.username});
}
});
Helper:
Template.newChatDetails.helpers({
newChatters: function() { return NewChatters.find(); }
});
To mimick the behaviour of Session without polluting the Session, use a ReactiveVar:
Template.contactsLayout.created = function() {
this.data.newChatters = new ReactiveVar([]);
}
Template.contactsLayout.events({
'click #contactItem': function (event, template) {
...
template.data.newChatters.set(
template.data.newChatters.get().push(this.username)
);
...
Then, in the inner template, use the parent reactive data source:
Template.newChatDetails.helpers({
newChatters: function() {
return Template.parentData(1).newChatters.get();
}
});
for people who is looking for a workaround for this in the year 2015+ (since the post is of 2014).
I'm implementing a posts wizard pw_module where I need to update data reactively depending on the route parameters:
Router.route('/new-post/:pw_module', function(){
var pwModule = this.params.pw_module;
this.render('post_new', {
data: function(){
switch (true) {
case (pwModule == 'basic-info'):
return {
title: 'Basic info'
};
break;
case (pwModule == 'itinerary'):
return {
title: 'Itinerary'
};
break;
default:
}
}
});
}, {
name: 'post.new'
});
Later in the template just do a:
<h1>{{title}}</h1>
Changing routes
The navigation that updates the URL looks like this:
<nav>
Basic info
Itinerary
</nav>
Hope it still helps someone.
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 :)