I'm having trouble getting a button click event to register in backbone. The event I have listed right before it (keypress in an input) is working fine.
Here is my View:
App.Views.TaskAddForm = Backbone.View.extend({
tagName: 'div',
initialize: function(){
},
events: {
'keypress #newTask': 'createNewTask',
'click #newTaskBtn': 'createNewTask',
},
template: App.Templates.TaskAddForm,
render: function(){
this.$el.html(this.template());
this.$el.attr('id','taskAddForm');
if (!this.newTask){
this.newTask = this.$('#newTask');
this.newPriority = this.$('#newPriority');
}
return this;
},
createNewTask: function(e){
if (e.keyCode !== 13) {
return;
}
var task = this.newTask.val();
var priority = this.newPriority.val();
if ($.trim(task).length > 0){
this.collection.add({name: task, priority: priority});
}
this.clearForm();
},
clearForm: function(){
this.newTask.val('');
this.newPriority.val(1);
}
});
Here is my template:
<h2>Add Task</h2>
<div class="input-append">
<select id="newPriority" class="input-medium" style="margin-right: 20px;">
<option value="1">Now</option>
<option value="2">Today</option>
<option value="3">Tomorrow</option>
<option value="4">Sooner</option>
<option value="5">Later</option>
</select>
<input type="text" id="newTask" placeholder="New Task" class="input-xxlarge">
<button class="btn" type="button" id="newTaskBtn">Add Task</button>
</div>
You can see I'm not actually passing anything into this template, but I wanted to still use a template because I will be adding and removing this Add Form view to the page depending on what the user is working on.
The keypress on the input works. The click on the button right next to it, does not! I've even tried adding this.delegateEvents() in my render function, but nothing I do seems to get the button working. I feel like it has to be something relatively simple that I'm just missing! Any help? Thanks!
Your event is probably firing, but
if (e.keyCode !== 13) {
return;
}
Is causing an early return. The event object for click event has no keyCode, and it certainly isn't 13.
Just separate the event handlers into two methods, and have them call a common method:
events: {
'keypress #newTask': 'taskNameChanged',
'click #newTaskBtn': 'addButtonClicked',
},
taskNameChanged: function(e) {
if(e.keyCode === 13) this.createTask();
},
addButtonClicked: function() {
this.createTask();
}
createTask: function() {
//...
}
Try using the keyup instead - this may be causing an issue. The jquery docs state that it isn't an official event so may have different support across browsers etc. I always use keypress and can say for sure that works across the board.
Also are you positive the event isn't being fired? Have you tried sticking an alert at the start of the method? Sometimes the old-school approach can actually help!
Related
I love Bootstrap-Select and I am currently using it through the help of a directive made by another user joaoneto/angular-bootstrap-select and it works as intended except when I try to fill my <select> element with an $http or in my case a dataService wrapper. I seem to get some timing issue, the data comes after the selectpicker got displayed/refreshed and then I end up having an empty Bootstrap-Select list.. though with Firebug, I do see the list of values in the now hidden <select>. If I then go in console and manually execute a $('.selectpicker').selectpicker('refresh') it then works. I got it temporarily working by doing a patch and adding a .selectpicker('refresh') inside a $timeout but as you know it's not ideal since we're using jQuery directly in an ngController...ouch!So I believe the directive is possibly missing a watcher or at least something to trigger that the ngModel got changed or updated. Html sample code:
<div class="col-sm-5">
<select name="language" class="form-control show-tick"
ng-model="vm.profile.language"
selectpicker data-live-search="true"
ng-options="language.value as language.name for language in vm.languages">
</select>
<!-- also tried with an ng-repeat, which has the same effect -->
</div>
then inside my Angular Controller:
// get list of languages from DB
dataService
.getLanguages()
.then(function(data) {
vm.languages = data;
// need a timeout patch to properly refresh the Bootstrap-Select selectpicker
// not so good to use this inside an ngController but it's the only working way I have found
$timeout(function() {
$('.selectpicker, select[selectpicker]').selectpicker('refresh');
}, 1);
});
and here is the directive made by (joaoneto) on GitHub for Angular-Bootstrap-Select
function selectpickerDirective($parse, $timeout) {
return {
restrict: 'A',
priority: 1000,
link: function (scope, element, attrs) {
function refresh(newVal) {
scope.$applyAsync(function () {
if (attrs.ngOptions && /track by/.test(attrs.ngOptions)) element.val(newVal);
element.selectpicker('refresh');
});
}
attrs.$observe('spTheme', function (val) {
$timeout(function () {
element.data('selectpicker').$button.removeClass(function (i, c) {
return (c.match(/(^|\s)?btn-\S+/g) || []).join(' ');
});
element.selectpicker('setStyle', val);
});
});
$timeout(function () {
element.selectpicker($parse(attrs.selectpicker)());
element.selectpicker('refresh');
});
if (attrs.ngModel) {
scope.$watch(attrs.ngModel, refresh, true);
}
if (attrs.ngDisabled) {
scope.$watch(attrs.ngDisabled, refresh, true);
}
scope.$on('$destroy', function () {
$timeout(function () {
element.selectpicker('destroy');
});
});
}
};
}
One problem with the angular-bootstrap-select directive, is that it only watches ngModel, and not the object that's actually populating the options in the select. For example, if vm.profile.language is set to '' by default, and vm.languages has a '' option, the select won't update with the new options, because ngModel stays the same. I added a selectModel attribute to the select, and modified the angular-bootstrap-select code slightly.
<div class="col-sm-5">
<select name="language" class="form-control show-tick"
ng-model="vm.profile.language"
select-model="vm.languages"
selectpicker data-live-search="true"
ng-options="language.value as language.name for language in vm.languages">
</select>
</div>
Then, in the angular-bootstrap-select code, I added
if (attrs.selectModel) {
scope.$watch(attrs.selectModel, refresh, true);
}
Now, when vm.languages is updated, the select will be updated too. A better method would probably be to simply detect which object should be watched by using ngOptions, but using this method allows for use of ngRepeat within a select as well.
Edit:
An alternative to using selectModel is automatically detecting the object to watch from ngOptions.
if (attrs.ngOptions && / in /.test(attrs.ngOptions)) {
scope.$watch(attrs.ngOptions.split(' in ')[1], refresh, true);
}
Edit 2:
Rather than using the refresh function, you'd probably be better off just calling element.selectpicker('refresh'); again, as you only want to actually update the value of the select when ngModel changes. I ran into a scenario where the list of options were being updated, and the value of the select changed, but the model didn't change, and as a result it didn't match the selectpicker. This resolved it for me:
if (attrs.ngOptions && / in /.test(attrs.ngOptions)) {
scope.$watch(attrs.ngOptions.split(' in ')[1], function() {
scope.$applyAsync(function () {
element.selectpicker('refresh');
});
}, true);
}
Well, this is an old one... But I had to use it. This is what I added in the link(..) function of the directive:
scope.$watch(
_ => element[0].innerHTML,
(newVal, oldVal) => {
if (newVal !== oldVal)
{
element.selectpicker('refresh');
}
}
)
select2 provides some custom events and i want to be able to listen to them, particularly the 'select2-removed' or better yet its custom 'change' event, and I’ve been searching the internet for some example, but with no luck.
here's what I’ve done so far:
HTML:
<input type="hidden" class="form-control" id="tags" ui-select2="modal.tags" data-placeholder="Available Tags" ng-model="form.tags">
JavaScript (Angular)
$scope.form = {tags: []};
postalTags = [
{
id: 1,
text: 'Permanent Address'
},
{
id: 2,
text: 'Present Address'
}
];
$scope.modal {
tags: {
'data': postalTags,
'multiple': true
}
};
// I doubt this is going to work, since i think this is only going to
// listen on events emitted by $emit and $broadcast.
$scope.$on('select2-removed', function(event) {
console.log(event);
});
// I can do this, but then i will not know which was removed and added
$scope.$watch('form.tags', function(data) {
console.log(data);
});
What the user actually is doing here is edit the tags tagged to his/her address, and by edit I mean the user can tag new tags to his/her address or remove previously tagged tags. That's why I need to track which tags where added and which are removed.
UPDATE
I saw a plausible solution on this discussion, but i cant make the provided code to work with mine, so i did some workaround, and this is what i did.
so instead of adding the,
scope.$emit('select2:change', a);
somewhere around here,
elm.select2(opts);
// Set initial value - I'm not sure about this but it seems to need to be there
elm.val(controller.$viewValue)
I put it here,
if (!isSelect) {
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
elm.bind("change", function (e) {
// custom added.
scope.$emit('select2:change', e);
e.stopImmediatePropagation();
then i did the usual on my controller,
$scope.$on('select2:change', function(event, element) {
if(element.added) console.log(element.added);
if(element.removed) console.log(element.removed);
}
and works just fine.
But i doubt this is very good idea, and i'm still hoping for a better solution.
I used a directive to encapsulate the select and then in the link function I just retrieve the select element and at the event handler.
Markup:
<select name="id" data-ng-model="tagsSelection" ui-select2="select2Options">
<option value="{{user.id}}" data-ng-repeat="user in users">{{user.name}}</option>
</select>
Javascript:
link: function (scope, element, attributes) {
var select = element.find('select')[0];
$(select).on("change", function(evt) {
if (evt.added) {
// Do something
} else if (evt.removed) {
// Do something.
}
});
$scope.select2Options = {
multiple: true
}
}
I'm using Angular and Bootstrap.
I'm trying to replicate the functionality of ng-model for bootstrap checkbox. What i would like to accomplish is:
i would like that when i click on the checkbox (label really) the model change, and actually that works... but what does not work that when i try to watch the object for changes the behavior is weired, because i need two click insted of one for disable or enable the checkbox.
Moreover if inside the the label element that has as attribute cm-checkbox="model.prop" i put a {{model.anotherprop}} wont work (does not render anything).
From the documentation i understood that because i want the two-way data bind the scope must be defined as i did.
Thank you for your help!
I have the following HTML:
<label id="claim_free" class="checkbox" for="checkbox1" cm-checkbox="model.prop">
<span class="icons"><span class="first-icon fui-checkbox-unchecked"></span><span class="second-icon fui-checkbox-checked"></span></span><input name="claim_free" type="checkbox" value="" data-toggle="checkbox">
same lable text
</label>
And the following JS:
directive('cmCheckbox', function() {
return {
restrict: 'A',
scope: {'cmCheckbox':'='},
link: function(scope,elm,attrs) {
scope.$watch('cmCheckbox', function() {
console.log("first value for "+attrs.cmCheckbox+" is: "+scope.cmCheckbox);
if (!scope.cmCheckbox) {
console.log("checked");
$(elm).removeClass("checked");
$(elm).children("input").removeAttr("checked");
} else { // false and undefined
console.log("unchecked");
$(elm).addClass("checked");
$(elm).children("input").attr("checked","checked");
}
});
$(elm).on('click', function(e) {
e.preventDefault();
var currentValue = elm.hasClass("checked") ? false : true;
scope.$apply(function() {
scope.cmCheckbox = currentValue;
},true);
scope.$parent.$apply();
});
}
};
}).
Here is the jsFiddle: jsfiddle.net/pmcalabrese/66pCA/2
I am a backbone newbie and I am trying to develop a Todo like App.
I have a Main view which is a list view and it has subviews. - the subview content can be edited on double click and it would get saved when the enter key is pressed. - very similar to todo example given in backbone github code.
var SubView = Backbone.View.extend({
tagName: "li",
events: {
"dblclick" : "show_edit_view",
"blur .element" : "close_edit_view",
"keypress .element" : "save_edit_view",
"click button.remove" : "remove_question"
},
initialize: function(){
this.render();
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html(_.template($("#sub_view_template").html(),this.model.toJSON()));
return this;
},
show_edit_view: function() {
this.$el.find("div.view").addClass("no_show");
this.$el.find("input").removeClass("no_show");
},
close_edit_view: function(){
this.$el.find("div.view").removeClass("no_show");
this.$el.find("input").addClass("no_show");
},
save_edit_view: function(e){
if (e.keyCode == 13) {
this.model.save({name: e.currentTarget.value});
this.close_edit_view();
}
}
});
And the template for this is
<script id="sub_view_template" type="text/x-template">
<div class="view"><%= name %></div>
<input class="element no_show" value="<%= name %>" type="text" /> <button class="remove">Remove</button>
</script>
This one works fine, the model is updated in the view and the update post request is sent to the server.
But, when I change the initialization and save_edit_view functions, only the first change event is fired and not the change events.
initialize: function(){
this.render();
this.listenTo(this.model, "change", this.render);
this.input = this.$("input.element");
},
save_edit_view: function(e){
if (e.keyCode == 13) {
this.model.save({name: $(this.input).val()});
this.close_edit_view();
}
}
I was wondering what could the problem be?
Thanks for any help!!!
The problem is you are referring to only one object. This means when you make the assignment:
this.input = this.$('input.element'); // match the current elements.
You are only getting the value from that exact object. After the first change, this.input is not the same object that contains your new value, and fails to save the model with a new value.
A demonstration that may help:
console.log(this.$('input.element') != this.$('input.element')); // true
This is why the following would work:
save_edit_view: function(e){
if (e.keyCode == 13) {
this.model.save({name: this.$('input.element').val()});
this.close_edit_view();
}
}
I guess this.$("input.element"); refers to the first item from the list.
And when you first time change model value with the value from the first item it works. But second time it doesn't works because the value of the first item still the same.
That is why you have to get input value from the event - e.currentTarget.value
Is there a way to have an element set up so that it performs one action on left-click (ng-click) and then another action on a right-click?
Right now I have something like:
<span ng-click="increment()">{{getPointsSpent()}}</span>
And I'd like to also be able to right click on the span to perform the function decrement();
You can use a directive to bind specific action on right click, using the contextmenu event :
app.directive('ngRightClick', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.ngRightClick);
element.bind('contextmenu', function(event) {
scope.$apply(function() {
event.preventDefault();
fn(scope, {$event:event});
});
});
};
});
Code example on fiddle
Hi this is an old question but I have a solution that I think may be simpler in some cases. The ngMousedown (and ngMouseup) directives are triggered by the right mouse button and have access to the original mouse event through $event so you could do it this way:
<span ng-mousedown="handleClick($event)"
oncontextmenu="return false"> <!-- use this to prevent context menu -->
{{getPointsSpent()}}
</span>
Then in the controller, you can do the following:
$scope.handleClick(evt) {
switch(evt.which) {
case 1:
increment(); // this is left click
break;
case 2:
// in case you need some middle click things
break;
case 3:
decrement(); // this is right click
break;
default:
alert("you have a strange mouse!");
break;
}
}
Here is a working fiddle. It works the same as the accepted answer but doesn't require the creation of a whole new directive. Although a directive may be a better solution, especially if you plan to attach right-click functions to lots of things. But anyway, another option.
One way is using a directive that binds an event handler to contextmenu event. I had hard time stopping bubbling to prevent default menu to show up so added native script handler for document. Tried with e.stopPropagation(), e.preventDefault() , return false etc . Checking for target in document handler seems to work well
app.directive('rightClick',function(){
document.oncontextmenu = function (e) {
if(e.target.hasAttribute('right-click')) {
return false;
}
};
return function(scope,el,attrs){
el.bind('contextmenu',function(e){
alert(attrs.alert);
}) ;
}
});
<button right-click alert="You right clciked me">Right click me</button>
DEMO http://plnkr.co/edit/k0TF49GVdlhMuioSHW7i
You can use this directive.
<div ng-controller="demoCtrl" save-content="classic-html">
<div contextmenu="{{lists}}" class="box" click-menu="clickMenu(item)" right-click="rightClick($event)">
<span>normal dropmenu</span>
</div>
</div>
<script type="text/javascript">
angular.module('demo', ['ngContextMenu'])
.controller('demoCtrl', ['$scope', function($scope) {
$scope.lists = [{
name: '11'
}, {
name: '22'
}]
$scope.clickMenu = function (item) {
console.log(item);
};
$scope.rightClick = function (event) {
console.log(event);
};
}])
</script>