I have the following function:
<script type="text/javascript">
$(function(){
// start a counter for new row IDs
// by setting it to the number
// of existing rows
var newRowNum = 2;
// bind a click event to the "Add" link
$('#addnew').click(function() {
// increment the counter
newRowNum += 1;
// get the entire "Add" row --
// "this" refers to the clicked element
// and "parent" moves the selection up
// to the parent node in the DOM
var addRow = $(this).parent().parent();
// copy the entire row from the DOM
// with "clone"
var newRow = addRow.clone();
// set the values of the inputs
// in the "Add" row to empty strings
//$('input', addRow).val('');
//$('name', addRow).val('os' + newRowNum);
// replace the HTML for the "Add" link
// with the new row number
$('td:first-child', newRow).html('<input type="hidden" name="on' + newRowNum + '" value="Email Address ' + (newRowNum - 1) + '">Recipient');
// insert a remove link in the last cell
$('td:last-child', newRow).html('<a href="" class="remove">Remove<\/a>');
// loop through the inputs in the new row
// and update the ID and name attributes
$('input:hidden', newRow).attr('id','on' + newRowNum ).attr('name','on' + newRowNum );
$('input:text', newRow).attr('id','os' + newRowNum ).attr('name','os' + newRowNum );
// insert the new row into the table
// "before" the Add row
addRow.before(newRow);
document.tp01.quantity.value = newRowNum-1;
// add the remove function to the new row
$('a.remove', newRow).click(function(){
$(this).parent().parent().remove();
return false;
});
// prevent the default click
return false;
});
});
</script>
This function is called by clicking on a link in a form (this function adds or removes rows from a table). The link looks like this:
<a id="addnew" href="">Add</a>
I need to put more forms on the same page accessed by a link in each of those forms that is, as far as the user is concerned, exactly the same as the one shown above. Can someone make suggestions as to how I can reuse the same function to accomplish this?
Thanks
Dave
move the embedded script to an external JS file:
var newRowNum = 2;
var bindLink = function(linkSelector)
{
// bind a click event to the "Add" link
$(linkSelector).click(function() {
...
}
}
Then in place of the embedded script put a call to bindLink('#addnew') in your ready handler.
Related
I have a function that records clicks on a link with a data tag. The code hides a telephone number until the link has been clicked, then it reveals the number. The problem I'm having is I am unable to get that revealed number to fire without user interaction once it has loaded.
I had to prevent the default event for when the button is clicked for the first time as the url does not yet contain the real data and also I don't want the page to jump.
Once clicked the new link's html will change to, example: "☎ 0123456789"
and the href will change to, example: "tel:0123456789"
This data is obtained from JSON sent from AJAX PHP file.
After this I want to programatically click the URL but I can't seem to get it to fire and keep getting a console error message saying "Uncaught TypeError: event is undefined" ~ presumably to do with the event prevent default?
Info: there will be multiple phone numbers loaded via PHP so I need to access each one individually.
HTML for the link;
<a data-num='$number' class='telephone' href='#'>☎ Click to call</a>
JS code (jQuery 3.4.1);
$(document).on('click','.telephone',function(event) {
event.preventDefault(); // Prevent page jump
var num = $(this).data("num");
$.ajax({
url: "myserverfile.php",
method: "POST",
data: {num: num},
dataType: "json",
success: function(a) {
var b = (a[0]['status']); // True or false
var c = (a[0]['tel']); // Number or error
// If result found change URL and click it to call number
if (b == true) {
$('.telephone[data-num="' + num + '"]').html("☎ " + c); // Change link text
$('.telephone[data-num="' + num + '"]').attr("href", "tel:" + c); // Change link href
$('.telephone[data-num="' + num +'"]').click(); // Update: This causes a loop because it runs the code again. I need to hide the original a tag and create a new one then trigger it.
}
}
});
});
Any suggestions?
Calling the .click() method doesn't create an event. The event is created when the user actually clicks something. If you console.log(event) it'll be undefined when called by .click(), which is where you're getting the error from. To get around that you could
if (event) { event.preventDefault(); }
But then you're going to run into recursion since you're basically
$(document).on('click', '.telephone', function() { $('.telephone').click(); });
To get around that you could add something like a loaded class
$(document).on('click', '.telephone:not(.loaded)', function() {
$('.telephone[data-num="' + num +'"]')
.addClass('loaded')
.attr(...)
.html(...)
.click();
});
Also, you'll thank yourself later for using a little more descriptive variable names. And for those unaware, jquery is chainable.
Figured it out. I was creating a loop so the click wouldn't work so I hid the first link after the click and loaded a new hidden one. I used a different column value from the table for the data tag on this new link to avoid any possible conflicts.
Apart from the loop issue the major part I was missing was [0] before assigning .click.
Thanks for the suggestions along the way.
Working code below;
$(document).on('click', '.telephone', function(event) {
event.preventDefault();
var num = $(this).data("num");
$.ajax({
url: "myserverfile.php",
method: "POST",
data: {num: num},
dataType: "json",
success: function(a) {
var b = (a[0]['status']);
var c = (a[0]['id']);
var d = (a[0]['tel']);
if (b == true) {
$('.telephone[data-num="' + num + '"]').hide(200);
$(document).ready(function() {
$(".telephone-reveal");
$('.telephone-reveal[data-id="' + c + '"]').show(200);
$('.telephone-reveal[data-id="' + c + '"]').html("☎ " + d);
$('.telephone-reveal[data-id="' + c + '"]').attr("href", "tel:" + d);
$('.telephone-reveal[data-id="' + c + '"]')[0].click();
});
}
}
});
});
<input type="file"/> The file and it path is cleared after clicking cancel in 'choose file' modal window in chrome, in FF and IE file stays untouched after pressing cancel. Is there any way to change this behavior in chrome?
https://jsfiddle.net/dqL97q0b/1/
Here is a work around, so that Chrome cannot remove the users existing file when cancel is pressed.
Code Notes:
This makes a clone of the Dom Element when User opens the file chooser if there is already a file selected.
Then if the user does click cancel in chrome it fires the change Event Listener and the value Will be "", so in that specific case I remove the now empty file chooser, and restore the clone.
Note: each file chooser Dom Element needs a unique id, so that the clone can be stored and retrieved properly.
Note: Most of the code is just logging, to show how things work, specifically I wanted to highlight that if you use the inline event listeners in the Dom Element such as onclick="fileClicked(event)" then you do not need to re-attach event listeners to the clone.
<!doctype html><html><head></head><body>
<h2>Fix for Chrome Removing File when 'cancel' clicked</h2>
Upload Image: <input id="imageUpload" type="file" onclick="fileClicked(event)" onchange="fileChanged(event)">
<br/><br/>
<label for="videoUpload">Upload Video:</label> <input id="videoUpload" type="file" onclick="fileClicked(event)" onchange="fileChanged(event)">
<br/><br/>
<div id="log"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
//This is All Just For Logging:
var debug = true;//true: add debug logs when cloning
var evenMoreListeners = true;//demonstrat re-attaching javascript Event Listeners (Inline Event Listeners don't need to be re-attached)
if (evenMoreListeners) {
var allFleChoosers = $("input[type='file']");
addEventListenersTo(allFleChoosers);
function addEventListenersTo(fileChooser) {
fileChooser.change(function (event) { console.log("file( #" + event.target.id + " ) : " + event.target.value.split("\\").pop()) });
fileChooser.click(function (event) { console.log("open( #" + event.target.id + " )") });
}
}
var clone = {};
// FileClicked()
function fileClicked(event) {
var fileElement = event.target;
if (fileElement.value != "") {
if (debug) { console.log("Clone( #" + fileElement.id + " ) : " + fileElement.value.split("\\").pop()) }
clone[fileElement.id] = $(fileElement).clone(); //'Saving Clone'
}
//What ever else you want to do when File Chooser Clicked
}
// FileChanged()
function fileChanged(event) {
var fileElement = event.target;
if (fileElement.value == "") {
if (debug) { console.log("Restore( #" + fileElement.id + " ) : " + clone[fileElement.id].val().split("\\").pop()) }
clone[fileElement.id].insertBefore(fileElement); //'Restoring Clone'
$(fileElement).remove(); //'Removing Original'
if (evenMoreListeners) { addEventListenersTo(clone[fileElement.id]) }//If Needed Re-attach additional Event Listeners
}
//What ever else you want to do when File Chooser Changed
}
</script>
</body></html>
https://jsfiddle.net/dqL97q0b/1/
Yes, that's been an issue with chrome if you want a jquery based solution you could use the following.
// Defining a global variable to persist data
let $old_value;
// if input field id is file and its inside a div which has id file-wrapper
$('#file_wrapper').on('change', '#file', function() {
const img = this.files[0];
// If image selected
if (img) {
// Change thumbnail or what your want
// Cloning the input filed value for future
old_value = $(this).clone();
} else {
// If no file selected or canceled and previously selected an image
if (old_value) {
$(this).remove();
let new_img = $('#file_wrapper').append(old_img);
old_img = new_img.clone();
}
}
}
What this doesn't cover
This solution doesn't cover Chrome's initial reason for not introducing this natively, a remove button. Though, with this considered, a remove button would be a super simple implementation.
What does this do?
This code saves the chosen file to an array as an object whenever a file is chosen on the input, retrieving that whenever the file input does not specify a file choice. (cancelled)
Solution
// Globally declare the array
// If your javascript isn't refreshed each time you visit the inputs, you'll want to clear this on form submit
array = []
// Add New file
// Replace current file
$("input").change(function(event) {
let file_list = event.target.files
let key = event.target.id
// When we change the files in our input, we persist the file by input
// Persistence before assignment will always result in the most recent file choice
persist_file(array, file_list, key)
// Assign the targets files to whatever is persisted for it
event.target.files = element_for(array, key).file_list
});
// #doc Pushes or Replaces {key: <key>, file_list: <FileList>} objects into an array
function persist_file(array, file_list, key) {
if(file_list.length > 0) {
if(member(array, key)) {
element_for(array, key).file_list = file_list;
}else {
array.push({key: key, file_list: file_list})
}
}
}
// #doc Determines if the <key> exists in an object element of the <array>
function member(array, key) {
return array.some((element, index) => {
return element.key == key
})
}
// #doc Get element in array by key
function element_for(array, key) {
return array.find((function(obj, index) {return obj.key === key}))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type='file' id="input_0"/><br>
<input type='file' id="input_1"/><br>
<input type='file' id="input_2"/>
Simple yet effective solution: keep a reference to the file in a variable. Every time the change event of the input is fired you check:
if(input.files[0]) // truthy, falsey
{
var file = input.files[0];
}
So file will contain the latest selected file and when user opens the dialog but cancelled out the file reference won't be changed and still contains the previously selected file.
First of all, I'm not sure if my title describes the problem correctly... I did search but didn't find anything that helped me out...
The project I'm working on has an #orderList. All orders have a delete option. After an order gets deleted the list is updated.
Sounds simple... I ran into a problem though.
/**
* Data returned at the end of selecting some options
*/
$.post(myUrl, $('#myForm').serialize(), function(data) {
// I build the orderlist
// The data returned is a JSON object holding session data (including orders)
buildOrderList(data);
...
// Do some other work
});
/*
* function to build the html list
*/
function buildOrderList(data) {
// Empty list
$('#orderList').empty();
// The click handler for the delete button is in here because it needs the data object
$(document).on('click', '[id^=delete_]', function() {
// Get the orderId from the delete button
var orderId = $(this).attr('id').split('_');
orderId = orderId['1'];
// I call the delete function
deleteOrder(orderId, data);
});
var html = '';
// Loop the data object
$.each(data, function(key,val){
...
// Put html code needed in var html
...
});
$('#orderList').append(html);
}
/*
* function to delete an order
*/
function deleteOrder(orderId, data) {
// Because of it depends on other 'products' in the list if the user can
// simply delete it, I use a jQuery dialog to give him some options.
// These options I send to a php script so it knows what should be deleted.
// This fires when a user clicks on the 'delete' button from a dialog.
// The dialog uses data to show options but does not change the value of data.
switch(data.type) {
case 'A':
delMsg += '<p>Some message for case A</p>';
delMsg += '<select>with some options for case A</select>';
$('#wizard_dialog').append(delMsg);
$('#wizard_dialog').dialog('option', 'buttons', [
{ text: "Delete", click: function() {
$.post(myUrl, $('#myDeleteOptions').serialize(), function(newData) {
// Now the returned data is the updated session data
// So I build the orderList again...
buildOrderList(newData);
...
// Do some other work
});
$( this ).dialog( "close" );
$(this).html(''); }},
{ text: "Cancel", click: function() { $( this ).dialog("close"); $(this).html(''); }}
] );
break;
case 'B':
// Do the same thing but different text and <select> elements
break;
}
}
The orderList updates correctly, however if I try to delete another order, the jQuery dialog gives me the option for the current (correct product) AND the option for the product that previously owned the id of the current. (Hope I didn't loose anyone in my attempt to explain the problem)
The main question is how to 'refresh' the data send to buildOrderList.
Since I call the function in a new $.post with fresh data object returned it should work, shouldn't it?
/**
* Enable the JQuery dialog
* (#wizard_dialog)
* this is the init (note that I only open the dialog in deleteOrder() and set text and buttons according to the data send to deleteOrder() )
*/
$('#wizard_dialog').dialog({
autoOpen: false,
resizable: false,
modal: true,
dialogClass: "no-close",
open: function() {
$('.ui-dialog-buttonpane').find('button:contains("Annuleren")').addClass('cancelButtonClass');
$('.ui-dialog-buttonpane').find('button:contains("Verwijderen")').addClass('deleteButtonClass');
$('.ui-dialog :button').blur(); // Because it is dangerous to put focus on 'OK' button
$('.ui-widget-overlay').css('position', 'fixed'); // Fixing overlay (else in wrong position?)
if ($(document).height() > $(window).height()) {
var scrollTop = ($('html').scrollTop()) ? $('html').scrollTop() : $('body').scrollTop(); // Works for Chrome, Firefox, IE...
$('html').addClass('noscroll').css('top',-scrollTop); // Prevent scroll without hiding the bar (thus preventing page to shift)
}
},
close: function() {
$('.ui-widget-overlay').css('position', 'absolute'); // Brake overlay again
var scrollTop = parseInt($('html').css('top'));
$('html').removeClass('noscroll'); // Allow scrolling again
$('html,body').scrollTop(-scrollTop);
$('#wizard_dialog').html('');
}
});
EDIT:
Because the problem could be in the dialog I added some code.
In the first code block I changed deleteOrder();
ANSWER
The solution was rather simple. I forgot to turn the click handler off before I added the new one. This returned the previous event and the new event.
$(document).off('click', '[id^=delete_]').on('click', '[id^=delete_]', function() {
// Get the orderId from the delete button
var orderId = $(this).attr('id').split('_');
orderId = orderId['1'];
// I call the delete function
deleteOrder(orderId, data);
});
I have array in my main.js page and I store that data in local storage using
//main page
$j.each(response.records, function(i, record) {
localStorage['sizes']=JSON.stringify(response.totalSize);
var names=[];
names[0]=getDate(record.StartDateTime);
localStorage['names']=JSON.stringify(names);
window.location = "theme.html";
});
//next page
var size = JSON.parse(localStorage['sizes']);
alert("Size in next page" + size);
for(var i=0;i<size;i++)
{
var storedNames = [];
storedNames=JSON.parse(localStorage['names']);
alert("Stored Array value " + storedNames);
}
record contain all data like date, time,location etc. I want to send complete record in next page, but in that page I am not able to parse that object. So I decided to send only date, but here count of date is 4, but in another page I got only last date store in array. So I store length of response in main page and fetch size in next page and after that loop is running, but again I got the same last value 4 times.
Please help me.
Thanks in advance
Try this:
//main page
var names=[];
$.each(response.records, function(i, record) {
names[i]=getDate(record.StartDateTime);
});
localStorage['names']=JSON.stringify(names);
window.location = "theme.html";
//next page
var storedNames=JSON.parse(localStorage['names']), allElements = [];
$.each(storedNames, function(i, el) {
allElements[i] = el;
});
alert("Stored Array value " + allElements);
Method UiInstance.getElementById(ID) always returns GenericWidget object, even if ID does not exist.
Is there some way how to find out that returned object does not exist in my app, or check whether UI contains object with given ID?
Solution for UI created with GUI builder:
function getSafeElement(app, txtID) {
var elem = app.getElementById(txtID);
var bExists = elem != null && Object.keys(elem).length < 100;
return bExists ? elem : null;
}
It returns null if ID does not exist. I didn't test all widgets for keys length boundary, so be careful and test it with your GUI.
EDIT: This solution works only within doGet() function. It does not work in server handlers, so in this case use it in combination with #corey-g answer.
This will only work in the same execution that you created the widget in, and not in a subsequent event handler where you retrieve the widget, because in that case everything is a GenericWidget whether or not it exists.
You can see for yourself that the solution fails:
function doGet() {
var app = UiApp.createApplication();
app.add(app.createButton().setId("control").addClickHandler(
app.createServerHandler("clicked")));
app.add(app.createLabel(exists(app)));
return app;
}
function clicked() {
var app = UiApp.getActiveApplication();
app.add(app.createLabel(exists(app)));
return app;
}
function exists(app) {
var control = app.getElementById("control");
return control != null && Object.keys(control).length < 100;
}
The app will first print 'true', but on the click handler it will print 'false' for the same widget.
This is by design; a GenericWidget is a "pointer" of sorts to a widget in the browser. We don't keep track of what widgets you have created, to reduce data transfer and latency between the browser and your script (otherwise we'd have to send up a long list of what widgets exist on every event handler). You are supposed to keep track of what you've created and only "ask" for widgets that you already know exist (and that you already know the "real" type of).
If you really want to keep track of what widgets exist, you have two main options. The first is to log entries into ScriptDb as you create widgets, and then look them up afterwards. Something like this:
function doGet() {
var app = UiApp.createApplication();
var db = ScriptDb.getMyDb();
// You'd need to clear out old entries here... ignoring that for now
app.add(app.createButton().setId('foo')
.addClickHandler(app.createServerHandler("clicked")));
db.save({id: 'foo', type: 'button'});
app.add(app.createButton().setId('bar'));
db.save({id: 'bar', type: 'button'});
return app
}
Then in a handler you can look up what's there:
function clicked() {
var db = ScriptDb.getMyDb();
var widgets = db.query({}); // all widgets
var button = db.query({type: 'button'}); // all buttons
var foo = db.query({id: 'foo'}); // widget with id foo
}
Alternatively, you can do this purely in UiApp by making use of tags
function doGet() {
var app = UiApp.createApplication();
var root = app.createFlowPanel(); // need a root panel
// tag just needs to exist; value is irrelevant.
var button1 = app.createButton().setId('button1').setTag("");
var button2 = app.createButton().setId('button2').setTag("");
// Add root as a callback element to any server handler
// that needs to know if widgets exist
button1.addClickHandler(app.createServerHandler("clicked")
.addCallbackElement(root));
root.add(button1).add(button2);
app.add(root);
return app;
}
function clicked(e) {
throw "\n" +
"button1 " + (e.parameter["button1_tag"] === "") + "\n" +
"button2 " + (e.parameter["button2_tag"] === "") + "\n" +
"button3 " + (e.parameter["button3_tag"] === "");
}
This will throw:
button1 true
button2 true
button3 false
because buttons 1 and 2 exist but 3 doesn't. You can get fancier by storing the type in the tag, but this suffices to check for widget existence. It works because all children of the root get added as callback elements, and the tags for all callback elements are sent up with the handler. Note that this is as expensive as it sounds and for an app with a huge amount of widgets could potentially impact performance, although it's probably ok in many cases especially if you only add the root as a callback element to handlers that actually need to verify the existence of arbitrary widgets.
My initial solution is wrong, because it returns false exist controls.
A solution, based on Corey's answer, is to add the setTag("") method and here is ready to use code. It is suitable for event handlers only, because uses tags.
function doGet() {
var app = UiApp.createApplication();
var btn01 = app.createButton("control01").setId("control01").setTag("");
var btn02 = app.createButton("control02").setId("control02").setTag("");
var handler = app.createServerHandler("clicked");
handler.addCallbackElement(btn01);
handler.addCallbackElement(btn02);
btn01.addClickHandler(handler);
btn02.addClickHandler(handler);
app.add(btn01);
app.add(btn02);
return app;
}
function clicked(e) {
var app = UiApp.getActiveApplication();
app.add(app.createLabel("control01 - " + controlExists(e, "control01")));
app.add(app.createLabel("control02 - " + controlExists(e, "control02")));
app.add(app.createLabel("fake - " + controlExists(e, "fake")));
return app;
}
function controlExists(e, controlName) {
return e.parameter[controlName + "_tag"] != null;
}