Knockout.js: Click binding only working first time I click? - javascript

Having some issues with a function in Knockout.js. Basically it is a menu where the first menu item "Översikt" should fetch a JSON array and populate the view.
The knockout code:
self.ongoingAuctions = ko.observableArray([]);
self.getOngoingAuctions = function(data) {
$.getJSON("assets/json/auctions.json", function(data) {
self.ongoingAuctions(data);
});
}
My click binding:
The problem is that this only works the first time I click on the menu item. The JSON doesn't get fetched the second, third, n:th time.
What am I doing wrong? Or have I misunderstood something?
Thanks in advance!

I have shared this fiddle for you that shows something else is wrong in your code that you havent not specified in the question:
It makes the call to the non existing json (in my case) every time you click
JS Fiddle to working code
var viewModel = function(){
var self = this;
self.ongoingAuctions = ko.observableArray([]);
self.getOngoingAuctions = function(data) {
$.getJSON("assets/json/auctions.json", function(data) {
self.ongoingAuctions(data);
});
}
self.setHeadline = function(){
console.log('set headline')
}
self.headline = function(){
console.log('headline');
}
}
var myVm = new viewModel();
ko.applyBindings(myVm);

Related

AngularJS - Shared service object being deleted incorrectly

When I trigger deleteQuestion() a second time 2 questions get deleted. Any idea? Let me know if you need to see more of my code.
controller.js
crtPromoCtrl.controller('surveyCtrl', ['$scope', 'surveySrv', function($scope, surveySrv)
{
$scope.questions = surveySrv.getQuestions();
$scope.editQuestion = function(index)
{
surveySrv.setEditQuestion(index);
};
$scope.deleteQuestion = function(index)
{
$(document).off('click', '#confirmationModal #confirm');
$('#confirmationModal').modal('show');
$(document).on('click', '#confirmationModal #confirm', function()
{
surveySrv.deleteQuestion(index);
$scope.$apply();
});
};
}]);
service.js
crtPromoSrv.service('surveySrv', function()
{
var questions = [];
var editQuestion;
this.getQuestions = function()
{
return questions;
};
this.addQuestion = function(question)
{
questions.push(question);
};
this.setEditQuestion = function(index)
{
editQuestion = questions[index];
};
this.getEditQuestion = function()
{
return editQuestion;
};
this.clearEditQuestion = function()
{
editQuestion = undefined;
};
this.deleteQuestion = function(index)
{
questions.splice(index, 1);
console.log(questions);
};
});
EDIT: I'm thinking it's an event propagation thing, since when I have 5 q's it deletes #2 and #3 when I delete #2.
EDIT: Fixed, see controller.js code.
It appears you are adding the 'click' function to your #confirmationModal #confirm button multiple times. The first time $scope.deleteQuestion is called, it adds the function. The second time you call it, it adds it again so when it is clicked, the function is called twice.
A simple fix would be to unbind the 'click' event before adding it again. Something like this: $('#confirmationModal #confirm').off('click');
The better solution here is to not use jQuery at all for these event bindings. Using a simple Angular modal directive (like the one provided in the Angular-UI library, for instance) would be the correct way to do this. Then you can just have an ng-click on the button and never have this problem.

Refresh Button Update Accordion in Javascript

I would like to edit accordion header (formationName) and once I click on the refresh button, it should update the accordion header. I couldn't figure out how to approach the problem.
$("#refresh").click(function(){
myData.offsetFormations[0]["FormationName"]="party";
json = JSON.stringify(myData);
alert( json );
});
fiddle: http://jsfiddle.net/xg7cr0g4/76/
It depends on your scenario. If there is a ton of data being refreshed often, you would want to do an in place edit.
If you just refresh when clicked and the data is insubstantial, just rebuild the table the way you originally did. An example of this (although it's not quite working):
http://jsfiddle.net/xg7cr0g4/78/
var build = function(){
//...build the grid/accordion here on demand (load,reload,programmatically)
};
var refresh = function(){
//update json
build(); //rebuild;
};
$(document).ready(function(){
// build on load
build();
});
jQuery UI accordion needs to be destroyed on rebuild:
re-initialize jquery accordion on callback
Here is the working fiddle:
http://jsfiddle.net/xg7cr0g4/79/
You can also organize your code better and avoid global functions by following the module pattern:
var Grid = function(){
var self = this;
this.build = function(){
...
};
this.rebuild = function(){
...
};
this.init = function(){
...
this.build();
$('#refresh').on('click', this.refresh);
...etc
};
$(this.init);
};
var grid = new Grid();
grid.rebuild();
To answer your other question, just add an additional method:
this.setHeader = function(header){
// similar to rebuild, change the json using param
x[...] = header;
this.build();
};

Scroll to bottom of page after get request AngularJs

I'm familiar with using something like:
$scope.gotoBottom = function(){
$location.hash('bottom');
$anchorScroll();
}
and this works.. yet what I'm seeing is an issue when retrieving data that's being used in an ng-repeat and attempting to resize when that data comes in.
Example (in controller):
users.get({userList:$routeParams.conversationId}, function(data){
$scope.userList = data;
$scope.gotoBottom();
})
The gotoBottom method is firing to fast, while the ng-repeat is looking on $scope.userList and buidling it's table based off that.
I want to be able to toggle gotoBottom after that list has been remade (or whenever it's modified). Is there a better way to achieve this?
Thank you!
You can use $watch listener to fire gotoBotton when an AngularJs variable change.
$scope.ActivityList = new Array();
$scope.$watch("ActivityList", function () {
$scope.$evalAsync(function () {
$scope.DoSomething();
});
}, true);
$scope.DoSomething = function () {
$(function () {
//$scope.gotoBottom();
});
};
Also you can run scrolling bottom after page is loaded
angular.element($window).bind('load',
function() {
var element = document.getElementById("messages-list").lastElementChild;
element.id = "bottom";
/*-------------*/
$location.hash('bottom');
$anchorScroll();
}

jQuery focus between .click() events

This is homework, just declaring it now.
I have to load a 'quiz' via XML (completed successfully), and generate td cells (done) to display said questions (not done, test data instead).
Here is my source code for the javascript
var selected;
var $cell;
var $cell2;
$(document).ready(function() {
$("#getTitle").click(function() {
selected = $("#quizname>option:selected").text();
$("#quiztitle").html(selected+" Quiz");
$("#quiz2").html(selected+" Quiz");
murl = "quizdata.xml";
$.ajax({type:"GET",
url:murl,
success:loaddata,
cache:false,
dataType:"xml",
data:selected,
error:ajaxerror
});
});
});
var $xml;
function loaddata(respobj,status,xhr) {
//to do:
//dynamic td creation for each xml question
$("#questions").empty();
$xml = $(respobj).find("quiz:contains("+selected+")");
for (var i=0;i<$xml.attr("qnum");i++) {
$('<tr>').attr("id","questions"+(i+1)).appendTo("#questions");
$("<td>").attr("id","question"+(i+1)).appendTo("#questions"+(i+1));
$("#question"+(i+1)).html((i+1)+". "+$xml.find("question[num='"+(i+1)+"']").text());
$("#question"+(i+1)).addClass("th.thirty");
$("<td>").attr("id","blank_question"+(i+1)).appendTo("#questions"+(i+1));
$("#blank_question"+(i+1)).addClass("question");
$("#blank_question"+(i+1)).html("Put Answer Here");
$("<td>").attr("id","answer"+(i+1)).appendTo("#questions"+(i+1));
$("#answer"+(i+1)).addClass("question");
$("#answer"+(i+1)).html((i+1)+". "+$xml.find("answer[num='"+(i+1)+"']").text());
$("#answer"+(i+1)).click(selectCell);
}
}
function selectCell() {
$cell = $(this);
$cell.css("background-color","red");
for (i=0;i<$xml.attr("qnum");i++) {
$("#blank_question"+(i+1)).click(function() {
$cell2 = $(this);
$cell.css("background-color","lightgray");
temp_text = $cell.text();
temp_id = $cell.attr("id");
$cell.attr("id",$cell2.attr("id"));
$cell.text($cell2.attr('id'));
$cell2.attr("id",temp_id);
$cell2.text(temp_id);
$("#answer"+(i+1)).unbind("click");
$("#answer"+(i+1)).bind("click", function() {
selectCell();
});
});
}
}
function swapCell() {
$cell.css("background-color","lightgray");
alert($(this).attr("id"));
}
function ajaxerror(xhr,status,error) {
$("td#desc").attr("class","");
$("td#desc").html("xhr="+xhr);
$("td#desc").append("<br /><br />status="+status)
$("td#desc").append("<br /><br />error="+error);
}
My issue is (try it here: HomeWork Link) that the first time you click the first cell, swap it with the second, it works. However, it only works every OTHER click and swap, which makes me think that there are some binding issues or focus issues because I need it to swap seamlessly. Is there an obvious error in the code or am I missing a specific focus/bind event?
Thanks!
Edit: the values being displayed AFTER swapping are the cells ID attribute
After googling "jquery recursive .click binding" I found that instead of .click() I changed it to .live() and that works perfectly.

Why does my jQuery event handler fail when attached to multiple elements?

I am using jquery to add mulitple new "addTask" form elements to a "ul" on the page every time a link is clicked.
$('span a').click(function(e){
e.preventDefault();
$('<li>\
<ul>\
<li class="sTitle"><input type="text" class="taskName"></li>\
<li><input type="button" value="saveTask" class="saveTask button"></li>\
</ul>\
</l1>')
.appendTo('#toDoList');
saveTask();
});
These new nested ul elements all have an button with the same class "saveTask". I then have a function that allows you to save a task by clicking on an button with the class "saveTask".
// Save New Task Item
function saveTask() {
$('.saveTask').click(function() {
$this = $(this);
var thisParent = $this.parent().parent()
// Get the value
var task = thisParent.find('input.taskName').val();
// Ajax Call
var data = {
sTitle: task,
iTaskListID: 29
};
$.post('http://localhost:8501/toDoLists/index.cfm/Tasks/save',
data, function(data) {
var newTask = '<a>' + task + '</a>'
thisParent.find('li.sTitle').html(newTask);
});
return false;
});
}
This essentially allows the user to enter some text into a form input, hit save, and then the task gets saved into the database using ajax, and displayed on the page using jQuery.
This works fine when there is only one element on the page with the class "saveTask", but if I have more than 1 form element with the class "saveTask" it stops functioning correctly, as the variable "var task" shows as "undefined" rather than the actual value of the form input.
Don't rely on the .parent() method. Use .closest('form') instead. So the following line:
var thisParent = $this.parent().parent()
should look something like this instead:
var thisParent = $this.closest('form');
EDIT:
Based on the updated information you provided, it looks like when you're trying to register the click event handler it's failing out for some reason. Try this javascript instead as it will make use of the live event so that all the newly added items on the page will automatically have the click event autowired to them.:
$(function(){
$('span a').click(function(e){
e.preventDefault();
$('<li>\
<ul>\
<li class="sTitle"><input type="text" class="taskName"></li>\
<li><input type="button" value="saveTask" class="saveTask button"></li>\
</ul>\
</l1>')
.appendTo('#toDoList');
});
$('.saveTask').live('click', function() {
$this = $(this);
var thisParent = $this.closest('ul');
// Get the value
var task = thisParent.find('input.taskName').val();
// Ajax Call
var data = {
sTitle: task,
iTaskListID: 29
};
$.post('http://localhost:8501/toDoLists/index.cfm/Tasks/save',
data, function(data) {
var newTask = '<a>' + task + '</a>'
thisParent.find('li.sTitle').html(newTask);
});
return false;
});
});
First turn the save task into a function:
(function($){
$.fn.saveTask= function(options){
return this.each(function(){
$this = $(this);
$this.click(function(){
var thisParent = $this.parent().parent()
//get the value
var task = thisParent.find('input.taskName').val();
// Ajax Call
var data = {
sTitle: task,
iTaskListID: 29
};
$.post('http://localhost:8501/toDoLists/index.cfm/Tasks/save', data, function(data){
var newTask = '<a>' + task + '</a>'
thisParent.find('li.sTitle').html(newTask);
});
});
});
return false;
})(jQuery)
When the app starts change the saveTask selector to this:
function saveTask(){
$('.saveTask').saveTask();
}
Then on your add function:
function addTask(){
$newTask = $("<div>Some Task stuff</div>");
$newTask.saveTask();
}
This code is written very quickly and untested but essentially create a jQuery extension that handles for data submission then when ever a task is created apply the save task extension to it.
I think you're looking for the live event.
Also, your code is a little awkward, since the click event is only added when the saveTask() function is called. In fact, the saveTask() function, doesn't actually save anything, it just adds the click event to the elements with the .saveTask class.
What is your HTML structure?
It looks like your code can't find the input.taskName element.
Try setting thisParent to something like $this.closest('form'). (Depending on your HTML)
You could try wrapping your click function in an each()
ie
function saveTask(){
$('.saveTask').each (function () {
$this = $(this);
$this.click(function() {
var thisParent = $this.parent().parent()
//get the value
var task = thisParent.find('input.taskName').val();
// Ajax Call
var data = {
sTitle: task,
iTaskListID: 29
};
$.post('http://localhost:8501/toDoLists/index.cfm/Tasks/save', data, function(data){
var newTask = '<a>' + task + '</a>'
thisParent.find('li.sTitle').html(newTask);
});
return false;
});
})
}
I find this helps sometimes when you have issues with multiple elements having the same class

Categories