Angular + DataTables: Clicking Rows Event w/JavaScript - javascript

I'm waiting until the data has been retrieved and then adding the data to the table and (trying to) add a click event to each row with the following code.
scope.$watch(attrs.aaData, function(value) {
var completeData = scope.$eval(attrs.allData);
var dataTable = element.dataTable(options);
var val = value || null;
if (val) {
dataTable.fnClearTable();
dataTable.fnAddData(scope.$eval(attrs.aaData));
var table = document.getElementsByClassName("dataTable")[1];
for (var i = 0, row; row = table.rows[i]; i++) {
console.log(row);
console.log(completeData[i]);
$(row).click(function(){
window.location.hash = '#/dashboard/patients/' + completeData[i].patient_id;
})
// javascript for click, but there should be a ng-click way
// $(row).attr('ng-click', 'changeView(/dashboard/patients/1)');
};
}
The console log confirms the row and completeData[i] are returning the correct values (and completeData[i]) has the patient_id component I want. Yet, when I click any row I get the following error:
Uncaught TypeError: Cannot read property 'patient_id' of undefined
Any ideas?

The issue is a scoping issue. You need to wrap your event handler in a closure.
for (var i = 0, row; row = table.rows[i]; i++) {
(function(i) {
console.log(row);
console.log(completeData[i]);
$(row).click(function(){
window.location.hash = '#/dashboard/patients/' + completeData[i].patient_id;
})
// javascript for click, but there should be a ng-click way
// $(row).attr('ng-click', 'changeView(/dashboard/patients/1)');
})(i);
}
Because as it stands now, this line:
$(row).click(function(){
window.location.hash = '#/dashboard/patients/' + completeData[i].patient_id;
})
will always refer i to the current value of i, not the value it had when the function was defined.
Alternatively, I recommend using jquery's $.each to clean up your loop and also build a closure at the same time:
$.each(table.rows, function(i, row) {
console.log(row);
console.log(completeData[i]);
$(row).click(function(){
window.location.hash = '#/dashboard/patients/' + completeData[i].patient_id;
})
// javascript for click, but there should be a ng-click way
// $(row).attr('ng-click', 'changeView(/dashboard/patients/1)');
});

Related

Getting Lost in Prototype Method Calls

apologies I realise there are a number of other threads that describe similar issues however I have not been able to find one that answers my question fully (at least in a way that I understand).
I use the following code to create an Object which manages the UI interactions for the various complicated instances of an input Form across my site.
Using Prototypes, I effectively end up with an object called categoryForm with various methods contained within:
- categoryForm.addEventListeners
- categoryForm.handlers
- categoryForm.validation
The last two are objects containing a number of different Methods.
The below code is a cut down version of my finished code, however should be sufficient to get the problem across, as the rest is variations on a similar theme.
The Issue I have is that, in the below example:
- I click '.addNewItems' on my table
- This triggers my listener, which calls the 'addNewTableItem' handler method.
- The handler then attempts to Loop through the Inputs, passing them through the 'validation.checkInputVal' method to validate each input before proceeding.
However, by the time we're in this loop, the scope of this has totally changed (as expected) and I have no idea how to refer to my categoryForm Object and call the 'validation.checkInputVal' method. I just get an error saying that this is not a function.(again expected)
Here's the code:
function categoryFormFuncs(){
// VARIABLES
var _this = this;
var table = $('table');
// EVENT LISTENER FUNCTIONS
this.addEventListeners = function(){
// Listen for the AddItemButton on Tables and call the addNewTableItem function
table.on('click', '.addNewItems', function(e){
// Find new ItemGroup and Collect Inputs into an Array
var newItemGroup = $(this).parents('.newItemGroup')[0];
// Send New Item Group and Table to be updated to the Handler
_this.handlers.addNewTableItem(newItemGroup);
});
}
};
// HANDLER FUNCTIONS
categoryFormFuncs.prototype.handlers = {
// Function to Create a NewItemGroup table row
addNewTableItem: function (inputGroup){
var validationcheck;
// Get all the Inputs
var inputs = $(inputGroup).find('input');
// Check Inputs are valid and Highlight them if not
for(var i = 0; i < inputs.length; i++){
validationcheck = validation.checkInputVal(inputs[i]);
if(!validationcheck.passed){
$(inputs[i]).addClass('input-inValid')
return
} else {
$(inputs[i]).removeClass('input-inValid')
}
};
// If Valid, turn each input into a Table Cell and clear the original Input value
var rowCells = ""
for(var i = 0; i < inputs.length; i++){
rowCells += "<td>" + $(inputs[i]).val() + "</td>";
$(inputs[i]).val("");
}
// Construct the new Table row and update the DOM
var newRow = "<tr class='itemGroup'>" + rowCells + "<td><span class='float-right remove-item fa fa-minus-circle'></span></td></tr>";
$(inputGroup).before(newRow);
}
}
// VALIDATION CHECKS
categoryFormFuncs.prototype.validation = {
checkInputVal: function(input){
if($(input).val()){
return { passed: true }
} else {
return { passed: false, message: "Input with no Value"}
}
}
}
var categoryForm = new categoryFormFuncs();
categoryForm.addEventListeners();
I have found one way to make this work which is to provide the validation method as an argument to the Handler:
function categoryFormFuncs(){
// VARIABLES
var _this = this;
var table = $('table');
// EVENT LISTENER FUNCTIONS
this.addEventListeners = function(){
// Listen for the AddItemButton on Tables and call the addNewTableItem function
table.on('click', '.addNewItems', function(e){
// Find new ItemGroup and Collect Inputs into an Array
var newItemGroup = $(this).parents('.newItemGroup')[0];
// Send New Item Group and Table to be updated to the Handler
_this.handlers.addNewTableItem(newItemGroup, _this.validation.checkInputVal);
});
}
};
// handlers
categoryFormFuncs.prototype.handlers = {
// Function to Create a NewItemGroup table row
addNewTableItem: function (inputGroup, checkInputVal){
var validationcheck;
// Get all the Inputs
var inputs = $(inputGroup).find('input');
// Check Inputs are valid and Highlight them if not
for(var i = 0; i < inputs.length; i++){
validationcheck = checkInputVal(inputs[i]);
if(!validationcheck.passed){
$(inputs[i]).addClass('input-inValid')
return
} else {
$(inputs[i]).removeClass('input-inValid')
}
};
// If Valid, turn each input into a Table Cell and clear the original Input value
var rowCells = ""
for(var i = 0; i < inputs.length; i++){
rowCells += "<td>" + $(inputs[i]).val() + "</td>";
$(inputs[i]).val("");
}
// Construct the new Table row and update the DOM
var newRow = "<tr class='itemGroup'>" + rowCells + "<td><span class='float-right remove-item fa fa-minus-circle'></span></td></tr>";
$(inputGroup).before(newRow);
}
}
// VALIDATION CHECKS
categoryFormFuncs.prototype.validation = {
checkInputVal: function(input){
if($(input).val()){
return { passed: true }
} else {
return { passed: false, message: "Input with no Value"}
}
}
}
var categoryForm = new categoryFormFuncs();
categoryForm.addEventListeners();
Alternatively I could also pass a reference to _this from the listener to the handler in order to access _this.validation.
Either way these all feel like very messy and clunky solutions.
My question is:
a) Is there a way, using my original deisgn to access the validation methods?
b) Is there a better established pattern for this type of Lister / Handler / Validation scenario that I should know about?
(Disclaimer: I am very new to programming (6 months) so apologies if my description is incorrect in any way.

Javascript array shows in console, but i cant access any properties in loops

I really try my damndest not to ask, but i have to at this point before I tear my hair out.
By the time the js interpreter gets to this particular method, I can print it to the console no problem, it is an array of "event" objects. From FireBug I can see it, but when I try to set a loop to do anything with this array its as if it doesn't exist. I am absolutely baffled......
A few things:
I am a newbie, I have tried a for(var index in list) loop, to no avail, I have also tried a regular old for(var i = 0; i < listIn.length; i++), and I also tried to get the size of the local variable by setting var size = listIn.length.
As soon as I try to loop through it I get nothing, but I can access all the objects inside it from the FireBug console no problem. Please help, even just giving me a little hint on where I should be looking would be great.
As for the array itself, I have no problems with getting an array back from PHP in the form of: [{"Event_Id":"9", "Title":"none"}, etc etc ]
Here is my code from my main launcher JavaScript file. I will also post a sample of the JSON data that is returned. I fear that I may be overextending myself by creating a massive object in the first place called content, which is meant to hold properties such as DOM strings, settings, and common methods, but so far everything else is working.
The init() function is called when the body onload is called on the corresponding html page, and during the call to setAllEvents and setEventNavigation I am lost.
And just to add, I am trying to learn JavaScript fundamentals before I ever touch jQuery.
Thanks
var dom, S, M, currentArray, buttonArray, typesArray, topicsArray;
content = {
domElements: {},
settings: {
allContent: {},
urlList: {
allURL: "../PHP/getEventsListView.php",
typesURL: "../PHP/getTypes.php",
topicsURL: "../PHP/getTopics.php"
},
eventObjArray: [],
buttonObjArray: [],
eventTypesArray: [],
eventTopicsArray: []
},
methods: {
allCallBack: function (j) {
S.allContent = JSON.parse(j);
var list = S.allContent;
for (var index in list) {
var event = new Event(list[index]);
S.eventObjArray.push(event);
}
},
topicsCallBack: function(j) {
S.eventTopicsArray = j;
var list = JSON.parse(S.eventTopicsArray);
topicsArray = list;
M.populateTopicsDropDown(list);
},
typesCallBack: function(j) {
S.eventTypesArray = j;
var list = JSON.parse(S.eventTypesArray);
typesArray = list;
M.populateTypesDropDown(list);
},
ajax: function (url, callback) {
getAjax(url, callback);
},
testList: function (listIn) {
// test method
},
setAllEvents: function (listIn) {
// HERE IS THE PROBLEM WITH THIS ARRAY
console.log("shall we?");
for(var index in listIn) {
console.log(listIn[index]);
}
},
getAllEvents: function () {
return currentArray;
},
setAllButtons: function (listIn) {
buttonArray = listIn;
},
getAllButtons: function () {
return buttonArray;
},
setEventNavigation: function(current) {
// SAME ISSUE AS ABOVE
var l = current.length;
//console.log("length " + l);
var counter = 0;
var endIndex = l - 1;
if (current.length < 4) {
switch (l) {
case 2:
var first = current[0];
var second = current[1];
first.setNextEvent(second);
second.setPreviousEvent(first);
break;
case 3:
var first = current[0];
var second = current[1];
var third = current[2];
first.setNextEvent(second);
second.setPreviousEvent(first);
second.setNextEvent(third);
third.setPreviousEvent(second);
break;
default:
break;
}
} else {
// do something
}
},
populateTopicsDropDown: function(listTopics) {
//console.log("inside topics drop");
//console.log(listTopics);
var topicsDropDown = document.getElementById("eventTopicListBox");
for(var index in listTopics) {
var op = document.createElement("option");
op.setAttribute("id", "dd" + index);
op.innerHTML = listTopics[index].Main_Topic;
topicsDropDown.appendChild(op);
}
},
populateTypesDropDown: function(listTypes) {
//console.log("inside types drodown");
//console.log(listTypes);
var typesDropDown = document.getElementById("eventTypeListBox");
for(var index2 in listTypes) {
var op2 = document.createElement("option");
op2.setAttribute("id", "dd2" + index2);
op2.innerHTML = listTypes[index2].Main_Type;
typesDropDown.appendChild(op2);
}
}
},
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, M.allCallBack);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
M.ajax(S.urlList.topicsURL, M.topicsCallBack);
M.ajax(S.urlList.typesURL, M.typesCallBack);
}
};
The problem you have is that currentArray gets its value asynchronously, which means you are calling setAllEvents too soon. At that moment the allCallBack function has not yet been executed. That happens only after the current running code has completed (until call stack becomes emtpy), and the ajax request triggers the callback.
So you should call setAllEvents and any other code that depends on currentArray only when the Ajax call has completed.
NB: The reason that it works in the console is that by the time you request the value from the console, the ajax call has already returned the response.
Without having looked at the rest of your code, and any other problems that it might have, this solves the issue you have:
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, function (j) {
// Note that all the rest of the code is moved in this call back
// function, so that it only executes when the Ajax response is
// available:
M.allCallBack(j);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
// Note that you will need to take care with the following asynchronous
// calls as well: their effect is only available when the Ajax
// callback is triggered:
M.ajax(S.urlList.topicsURL, M.topicsCallBack); //
M.ajax(S.urlList.typesURL, M.typesCallBack);
});
}

how to loop through rows in an HTML table to find a specific value?

I have the following sample html: https://jsfiddle.net/pgd8e46b/1/
What I'm trying to do is let the users click on a row from the "available_widgets" table and if it's not already in the "existing_widgets" table, add it and do other things.
I have the logic to grab the appropriate table and start the loop...
var check_for_duplicates = function (row_id) {
var table = document.getElementById('existing_widgets');
var rows = table.getElementsByTagName('td');
for (var i = 0, len = rows.length; i < len; i++) {
console.log(i);
}
console.log(rows);
return true;
}
but I don't know how to compare the ID field with the id that's passed in.
I still have to write the logic that strips out the "row_" prefix from the id that's passed to the check_for_duplicates() method.
Any suggestions would be appreciated.
I think what you are looking for is the id property in the returned elements.
rows[i].id should give you what you are looking for in your loop.
to strip off the prefix: rows[i].id.replace('row_', '') That will create a new string for you to compare against.
You can simplify check_for_duplicates function if you use jQuery to find widget row by id:
$('#available_widgets').on('click', 'tr', function () {
if (check_for_duplicates(this.id)) {
return false;
} else {
alert('otherwise, do other stuff');
}
});
var check_for_duplicates = function (row_id) {
return !!$('#existing_widgets').find('#' + row_id.replace('row_', '')).length;
}
This is a working function in JS. I would also suggest using some other prefix on the existing_widgets table, perhaps existing_row_#, and then you'd just need to modify the replace() component.
function check_for_duplicates(row_id) {
var table = document.getElementById('existing_widgets');
if (document.getElementById(row_id.replace('row_', ''))) {
console.log(row_id + ' is a duplicate');
return true;
} else {
console.log(row_id + ' is not a duplicate');
return false;
}
}

Inner function does not assign value to element dynamically created by outer function in jQuery

I am trying to create a search result dynamically added below the form through ajax call using parameter given in the form. I can add results using a table, but in these results I have client ids where I need Name, so I again use ajax call for each client id which return success but can not assign the values to corresponding table field. Below is my code, what is wrong here?
var stopMulti = 0;
$('#salesreport-end_date').change(function(){
var endDate = $(this).val();
var startDate = $('#salesreport-start_date').val();
var param = startDate+'#'+endDate+'#date';
if(stopMulti == 0){
$.get('index.php?r=reports/sales-report/sales-report',{ id : param }, function(data){
var sReport = JSON.parse(data);
if(typeof sReport != 'undefined'){
if(sReport.length != 0){
$('<div id="sales-report-div-1" class="col-sm-12"><h2 id ="sales-report-heading-1">Sales Report</h2></div>').insertAfter('#sub-button-div');
$('<table class="table-bordered text-center col-sm-12"><tr id="sales-report-row-head"><th class="text-center">Invoice Number</th><th class="text-center">Client Name</th><th class="text-center">Company Name</th><th class="text-center">Phone</th><th class="text-center">Net Total</th><th class="text-center">VAT</th><th class="text-center">Total</th></tr></table>').insertAfter('#sales-report-heading-1');
$('<br><br>').insertAfter('#sales-report-div-1');
var count = 0;
for(var i = 0; i < sReport.length; i++){
$('<tr><td id=sales-report-col-1'+count+'></td><td id=sales-report-col-2'+count+'></td><td id=sales-report-col-3'+count+'></td><td id=sales-report-col-4'+count+'></td><td id=sales-report-col-5'+count+'><td id=sales-report-col-6'+count+'></td><td id=sales-report-col-7'+count+'></td></tr></div>').insertAfter('#sales-report-row-head');
$('#sales-report-col-1'+count).html(sReport[i].invoice_id);
$.get('index.php?r=reports/sales-report/get-client',{ id : sReport[i].client_id}, function(client){
var client = JSON.parse(client);
$('#sales-report-col-2'+count).text(client.client_name);
$('#sales-report-col-3'+count).text(client.company_name);
$('#sales-report-col-4'+count).text(client.telephon);
});
$('#sales-report-col-5'+count).html(sReport[i].sub_total);
$('#sales-report-col-6'+count).html(sReport[i].taxrate);
$('#sales-report-col-7'+count).html(sReport[i].invoice_total);
count++;
}
}
}
});
}
stopMulti = 1;
});
That is I am getting blank fields for client name, company name and telephone.
Firebug does not find any error in the code.
The $.get callback function will be called asynchronously, and therefor any value like count will be changed before the .get returns.
A simple technique to overcome this is to use anonymous function, so your function should look like this:
for(var i = 0; i < sReport.length; i++)
{
f = function(i,count)
{
... rest of your code
}
f(i,count);
count++;
}
Here is a general example: https://jsfiddle.net/BentalSW/auyLq7rp/
You can't use count inside the inner AJAX Callback because by the time those asynchronous calls complete count will equal sReport.length and the wrong divs will be updated.
Use a .forEach loop instead of the for loop:
sReport.forEach(function(report, count) {
...
});
This will give you a value of count that is correctly bound to the current iteration count.
NB: it shouldn't be necessary to call JSON.parse on data that's already in JSON format - jQuery will do that for you automatically.

SlickGrid cannot delete added rows, but only existing ones. What am I doing wrong?

Here is my code for adding a row:
grid.onAddNewRow.subscribe(function (e, args) {
var item = args.item;
id=id+1;
item["id"] = "id_"+id;
grid.invalidateRow(data.length);
data.push(item);
dataView.beginUpdate();
dataView.endUpdate();
grid.updateRowCount();
grid.render();
});
And here is my code for deleting a row:
if ((event.which == 46)) {
$(".slick-cell.selected").parent().each(function() {
var item = dataView.getItem($(this).attr("row"));
var rowid = item.id;
dataView.deleteItem(rowid);
grid.invalidate();
grid.render();
});
}
This works for already existing rows but not for the added ones. for some reasons THE item variable is undefined for new rows. What am I doing wrong?
Edited
Thanks, Tin! So I`ve got a solution :
grid.onAddNewRow.subscribe(function (e, args) {
var item = args.item;
id=id+1;
item["id"] = "id_"+id;
data.push(item);
dataView.beginUpdate();
dataView.setItems(data);
dataView.endUpdate();
});
.
if ((event.which == 46)){
var rows = grid.getSelectedRows();
for (var i = 0, l = rows.length; i < l; i++) {
var item = dataView.getItem(rows[i]);
var rowid = item.id;
dataView.deleteItem(rowid);
}
}
You seem to be doing a lot of things in your code that don't quite make sense to me:
In your onAddNewRow handler, you are:
Invalidating the row being added. Why? You don't need to do that.
You update the "data" array directly but then do a no-op call on the "dataView". What are you using as a data source - data or dataView?
You don't need to tell the grid to updateRowCount() or render().
In your delete handler:
DO NOT access SlickGrid DOM directly (i.e. $(".slick-cell.selected"))! Use grid.getSelectedRows() instead.
If you are using "dataView" as your data source, you should already have events wired up to listen to DataView changes and update the grid as needed. Calls to grid.invalidate() and grid.render() are not needed.

Categories

Resources