Javascript: Dynamic Function with Parameter - javascript

This is kind of tricky, but I need to make the large code block below generic.
I have a number of views that follow the same format: A table full of rows, where each row contains a delete icon. Each icon has a data-id attribute which is the item's _id in the database.
I am wiring the click action on each icon to where it opens a standard dialog to ask for confirmation. As you can guess, the "Yes" button of the dialog will have an onclick which calls the desired function with a parameter of the item's _id. e.g., setting onclick = deleteContact(fdke75jdsgtd7i)
Let's say I have 3 tables: Contacts, Cases and Firms.
I got all the wiring to work for any given table, provided I copy and paste the following code block in every view with the caveat that I have to use the commented onclick line instead of the generic, uncommented line below it.
let deleteItemAnchors = document.getElementsByClassName("delete-item");
Array.from(deleteItemAnchors).forEach( (item) => {
item.addEventListener('click', () => {
// Highlight the selected row.
highlightedTableRow = item.closest("tr");
highlightedTableRow.classList.add("table-warning");
// The record's _id is in the data-id attribute.
let itemId = item.getAttribute("data-id");
let buttons = [{
//onclick: () => { removeTableRowHighlight(); deleteContact(itemId); },
onclick: () => { removeTableRowHighlight(); deleteFunction(itemId); },
text: "Yes"
}, {
onclick: () => { removeTableRowHighlight(); },
text: "No",
class: "btn-secondary"
}];
let confirmDelete = new CustomDialog("Delete this " + recordType + "?", 'Click "Yes" to delete it. Press "No" to cancel.', buttons);
});
});
Here is an example of the function called when the button is clicked:
function deleteContact(itemId) {
console.log("You deleted the item with id = " + itemId);
}
Bear in mind, it is only working if the commented onclick line is live, and the generic line right below it is comment out.
I want to stop recreating the big block of code for every view by moving it to a re-usable function which can be called from each of the 3 views as follows:
let deleteFunction = () => { deleteContact(); };
wireDeleteIcons("Contact", deleteFunction);
let deleteFunction = () => { deleteCase(); };
wireDeleteIcons("Case", deleteFunction);
So, I moved the code block to a function called, "wireDeleteIcons," which accepts:
the record type as a string, and
the function which does the deleting, like deleteCase() or deleteContact().
From my Contacts view, I am calling:
let deleteFunction = () => { deleteContact(); };
wireDeleteIcons("Contact", deleteFunction);
It is all working so far, except for the deleteFunction(itemId) call when the icon is clicked.
If you look back up at the big code block, check out the line below the commented onclick line.
I am trying to add the parameter, itemId, to the function that was passed. On testing, it makes it all the way to my deleteContact() function, but it doesn't pass in the _id. So, my console.log shows, per my deleteContact() function, "You deleted the item with id = undefined"
How can I pass the function generically, and insert the parameter into it from within my generic wireDeleteIcons() function?

Your deleteFunction() needs to take an argument:
let deleteFunction = (id) => deleteContact(id);
But you don't really need the deleteFunction variable. Just write:
wireDeleteIcons("Contact", deleteContact);
wireDeleteIcons("Case", deleteCase);
The definition of wireDeleteIcons should be something like this:
function wireDeleteIcons(tableId, deleteFunction) {
let deleteItemAnchors = document.getElementById(tableId).getElementsByClassName("delete-item");
Array.from(deleteItemAnchors).forEach( (item) => {
item.addEventListener('click', () => {
// Highlight the selected row.
highlightedTableRow = item.closest("tr");
highlightedTableRow.classList.add("table-warning");
// The record's _id is in the data-id attribute.
let itemId = this.getAttribute("data-id");
let buttons = [{
onclick: () => { removeTableRowHighlight(); deleteFunction(itemId); },
text: "Yes"
}, {
onclick: () => { removeTableRowHighlight(); },
text: "No",
class: "btn-secondary"
}];
let confirmDelete = new CustomDialog("Delete this " + recordType + "?", 'Click "Yes" to delete it. Press "No" to cancel.', buttons);
});
});
}

Related

adding click event to promise

I am having trouble with adding a click event to an element that only exists if another element on the page has been clicked. This is on the Amazon website just for context. I am trying to add an overlay at the bottom of the mobile screen which allows you to select the quantity you would want and add it to basket.
In order to do this I have created my own dropdown which allows you to select the quantity (selectNumber function) - what this does is replicate the click on the original dropdown and bring up the popover element which you click in order to select the quantity. I then targeted the element in the popover which represents the quantity you would like and added a click event on that too.
The issue I am having is that on the first time I click on my dropdown and change the quantity it replicates the click on the original dropdown but doesn't update the quantity. However, if I try it again after it works perfectly. I have a feeling that it is to do with the fact the popover does not exist until the first click of the dropdown, it then fails to fire the rest of my code. I am using a Promise function (waitforElem) in order to observe that the element now exists on the page. The second time - as the element now exists - it is able to fire the rest of my code, which allows you to update the quantity, just fine. I have tried to put all my code in the same .then function but in the below I have split it into two .then's. Neither seems to work. My code is below:
function waitForElm(selector) {
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector))
}
const observer = new MutationObserver(() => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector))
observer.disconnect()
}
})
observer.observe(document.body, {
childList: true,
subtree: true,
})
})
}
selectNumber().forEach((el) =>
el.addEventListener('input', () => {
const numberSelected = Number(el.options[el.selectedIndex].value)
console.log(numberSelected)
amazonDropDown().click()
const popoverDropdown = () =>
document.querySelectorAll(
'.a-popover.a-dropdown.a-dropdown-common.a-declarative .a-popover-wrapper .a-nostyle.a-list-link .a-dropdown-item'
)
waitForElm(
'.a-popover.a-dropdown.a-dropdown-common.a-declarative .a-popover-wrapper '
)
.then(() => {
console.log('Element is ready')
})
.then(() => {
document.querySelectorAll(
'.a-popover.a-dropdown.a-dropdown-common.a-declarative .a-popover-wrapper .a-nostyle.a-list-link .a-dropdown-item'
)
const popoverArr = () => Array.from(popoverDropdown())
const popoverEl = () =>
popoverArr().filter((el) => {
const numb = () => Number(el.firstElementChild.textContent)
return numb() === numberSelected
})
console.log(popoverEl())
for (let i = 0, len = popoverEl().length; i < len; i++) {
const firstChild = (): any => popoverEl()[i].firstElementChild
console.log(firstChild())
firstChild().click()
}
})
})
)
I really hope the above makes sense and that someone can help me on this as I have been banging my head on it for a whole day. Thank you in advance.

how to pass element in inline onclick event in a dynamically created string

I got a function that creates a button template for Kendo Grid Toolbar based on the parameter received. What I want to do is passing one of the properties inside the parameter called element(it is a jquery element) through onclick function so that I can access that element in the function fnAdd(element).
function(parameter)
{
var template="<button onclick='fnAdd("+param.element+")'>Add</button>";
$('#grid').kendoGrid({
toolbar: template
});
}
Currently, the onclick function is not working. I try to use JSON.stringify on the param and pass it through the onclick and able to get it on fnAdd(), but I can't access the param.element at all.
Anyone know how to solve this?
You can use the bind method to attach your params to achieve this. Sample code below
function add(param) {
let btn = document.createElement('button');
btn.innerHTML = 'NEW - ' + new Date().getTime();
btn.onclick = newButtonOnclick.bind(this, (param || new Date().getTime()))
document.body.append(btn)
}
function newButtonOnclick(param) {
alert(param);
}
button {
display: block;
margin-bottom: 1em;
}
<button type="button" onclick="add();">
Add New Button
</button>
I hope this solution can help you:
Remember the Patter designs, in this case we can use a Factory patter with this you can reuse this code to create chunks of code.
With this example also each button could trigger a different function and can have different params!
// YOUR JAVASCRIPT WITH ACCESS TO THE DOM
// here you call your plugin where the DOM is not present
function getButton() {
let newButtonInString = createButtonPlugin();
// in newButton you just have the string
// now convert it to html using Jquery
let htmlButton = $(newButtonInString);
document.body.append(htmlButton[0]);
}
// YOUR PLUGIN JS
let counter = 1;
function createButtonPlugin() {
// you can take this params from the UI
let paramsExample = {
text: `New Button ${counter}`,
functionParams: {
counter: counter
} // Could be an object or what you want!
}
let bp = new ButtonPrototype(paramsExample);
let newButton = bp.createButton();
counter++;
// return string to the place where you can render the button
return newButton;
}
class ButtonPrototype {
constructor(params){
this.params = params
}
createButton() {
return `<button onclick=fn(${JSON.stringify(this.params.functionParams)})>${this.params.text}</button>`
}
}
function fn (args) {
alert(`Success ${args.counter}`)
}
Your html:
<button onclick="getButton()">add</button>
I also leave the codepen: Just Updated with the new comment you made!
https://codepen.io/ruben-saucedo/pen/GbxXNG

How to dynamically create delete buttons which action the row it is in?

I've created an ASP.NET MVC5 app with signalR for the backend and using javascript for the front end (it's mainly for learning). Functionally, it's very simple it just allows any connected client to see a list of items and each client can add/delete an item. Any changes to the list are updated asynchronously on all clients.
I've got it working so I can add items which will dynamically create a new row in the table and each addition will make the corresponding call on the web server. Each row of the table adds a delete button but I'm not sure how to hook this up to a handler which can then make a signalR call to the server to remove the associated item to the delete button. Can anyone tell me how I can do this please?
My javascript code is here:
(function () {
var itemHub = $.connection.itemHub;
var updateItems = function (items) {
$("#item-list").empty();
var table = $("<br/><table border=\"1\" cellpadding=\"5\"></table>");
var tableRow = $("<tr><td><b>Name</b></td><td><b>Delete</b></td></tr>");
table.append(tableRow);
for (var i = 0; i < items.length; i++) {
tableRow = $("<tr><td>" + items[i] + "</td><td><input type=\"button\" value=\"Delete\" id=\"Delete" + i + "\"></td></tr>");
table.append(tableRow);
}
$("#item-list").append(table);
};
$("#submit").on("click",
function () {
var text = $("#textname").val();
itemHub.server.addItem(text)
.done(function () {
console.log("Adding item: " + text);
$("#textname").val("");
})
.fail(function (e) {
console.log(e);
});
});
itemHub.client.itemListUpdated = updateItems;
$.connection.hub.start()
.done(function () {
console.log("Connected to SignalR hub");
itemHub.server.getItems()
.done(function (items) {
updateItems(items);
})
.fail(function (e) {
console.log(e);
});
})
.fail(function (e) { console.log(e); });
})();
The server call to delete the item would be: itemHub.server.deleteItem(text) where "text" is the same string that was added.
You can do something like this:
$("body").on("click", "input[value=Delete]", function(){
var $row = $(this).parents("tr");
var item = $row.find("td").first().text();
itemHub.server.removeItem(item);// or whatever is the function for removing items.
$row.remove();
});
This works because in the callback of a jQuery event listener, this refers to the clicked element event.target.
So, what this does, is to get the row the clicked element is in, get the text of the first column of that row (the item) and remove the item and the row.
You should probably give all your delete buttons a class name so you can use that class name instead of input[value=Delete] as a selector.

Load select function on page load

I have select list like this:
function chargeC() {
apiService.get("../../api/Catalogos/", null,
function (res) {
$scope.Catalogos = res.data;
$scope.selected = $scope.Catalogos[0];
}, errorCatalogo);
}
As you can see my default value is $scope.Catalogos[0]
When I select one value of select list it load new function:
$scope.filtro = function (selected) {
apiService.get("../../api/Catalogo/GetCatalogoRegistro/" + selected.ID);
I want to load this second function when page load with my default value $scope.Catalogos[0], how can I achieve it?
I try with
select.firstElementChild.click();
or
$(chargeC).trigger($scope.filtro);
But donĀ“t work

Reusing a modal template

On my current project, there are starting to be a few views that are modal views that are being used to delete items on the site. They are currently generic in that it's just a text description of the item they are deleting. Maybe in the future there will be an icon or a short description as well. There are now tasks to have that functionality to delete other stuff on our site. I'm new to the web, MVC, asp.net, etc, and what I want to know is if it's better to reuse our current modal view somehow, and pass in the objects we need to show in the view. Because the view needs to send the url back to the server on which items to delete, that part of code would need to be different for the view as well. Here is some of the stuff in our view along with a .cshtml template that's pretty generic that I didn't include.
Views.DeleteGiftModal = (function () {
return Backbone.View.extend({
template: Templates["template-gift-delete-modal"],
tagName: 'div',
initialize: function (options) {
$(window).bind("disposeModal", _.bind(this.disposeModal, this));
_.bindAll(this, "showDialog", "disposeModal", "displayResults");
this.eventAggregator = options.eventAggregator;
this.itemsToDelete = options.model;
this.errors = {};
this.render();
return this;
},
events: {
"click #delete-btn": "deleteItems",
"click #ok-btn": "disposeModal",
"click #cancel-btn": "disposeModal"
},
disposeModal: function (event, refresh) {
this.$el.modal("hide");
if (event != null && event.currentTarget != null && event.currentTarget.id == 'ok-btn')
refresh = true;
this.trigger("modalClosed", refresh);
this.remove();
this.unbind();
},
showDialog: function () {
this.$el.modal("show");
},
deleteItems: function () {
var self = this;
var element = this.$el;
var numberGifts = this.getKeys(this.itemsToDelete).length;
this.results = [];
var hasError = false;
element.find("#actions").hide();
element.find("#ok-actions").show();
$.each(this.itemsToDelete, function(i, v) {
// tell model to go away
var gift = new Gift({ id: i });
gift.destroy({
success: function (model, response) {
self.results.push({ id: model.id, response: response });
numberGifts--;
if (numberGifts <= 0) {
if (!hasError) {
self.disposeModal(null, true);
} else {
self.displayResults();
}
}
}
});
});
},
displayResults: function () {
var element = this.$el;
$.each(this.results, function(i, v) {
// to do check response for error message
var list = element.find("#delete-item-" + v.id);
if (v.response.message == "Deleted") {
list.append(" - <span align='right' style='color: green'>Deleted</span>");
} else {
hasError = true;
list.append(" - <span align='right' style='color: red'>" + v.response.message + "</span>");
}
});
},
render: function () {
this.$el.append(this.template);
this.$el.find("#ok-actions").hide();
// show list of item names
var list = this.$el.find("#items-to-delete-list");
$.each(this.itemsToDelete, function (i, v) {
$("<li id='delete-item-" + i + "'>" + v.name + "</li>").appendTo(list);
});
this.$el.attr('id', 'delete-gift-dialog');
return this;
}
});
})();
As I am looking through the code, and this being my first real project, it seems like a lot of things that could be quite similar, like deleting a Gift, deleting a Toy, etc have different Controllers for each (GiftController, ToyController), and hit different URLs. So currently things are all in their own class like that. I was wondering if that's the more standard way to approach these types of problems as well with views. Thanks in advance!
The app we're developing at work had a similar issue. We're using Backbone too so I can completely relate to this. What I ended up doing is have a sort of ModalBuilder that builds a form in a modal for you and binds events on the form elements for submit. The initialization of it could look like this:
new ModalBuilder({
form: [
{
tag: 'select[name="id"]',
options: [
{ name: 'Item 1', id: 12 },
{ name: 'Item 2', id: 32 }
]
},
{
tag: 'input[type="submit"]',
value: 'Delete'
}
],
events: function(){
$('input[type="submit"]').on('click', function(){
// Delete via ajax
})
}
})
What we do is we have different templates for every form element, inputfields and textareas and so on and we reuse it all over the place. ModalBuilder takes these arguments and builds a form
Also for certain cases it might be better to render the form server-side and deliver it to your modal via ajax. You have to weigh what makes your app more performant I suppose.

Categories