The question is: how to check if the d3 behavior, such as drag or zoom, is already called on the element? Seems to be this task is in relation to getting the events, which are attached to element? What is the common approach?
Thanks.
I know this is old, but I came across this post looking for the same thing, saw there wasn't an answer, so created the answer my self. This is for d3.js Version 4.
function hasD3EventOn(ele, eventName, eventType) {
if (!ele || typeof eventName == 'undefined') return !1;
if (ele._groups) ele = ele._groups[0][0]; // if d3.select()
var on = ele.__on; //d3.js(v4) function onRemove(typename)
if (!on || !on.length) return !1; var i = on.length, o;
while (--i >= 0) { if ((o = on[i]).name && o.name === eventName && (typeof eventType == 'undefined' || (o.type && o.type === eventType))) return !0; } return !1;
}
Just send in either a d3.select element (first in array only) or a native DOM Element along with the event name such as 'zoom', both are reqired. There is also the option for the event type such as 'wheel'. It also has some basic validation (maybe excessive) to help prevent crashing when called with trash or property name collison.
Related
I have a parent table with rows.
When they select a row, an AJAX call fires that returns the child details.
I have multiple text boxes showing child properties
<div class="row">
#Html.CheckBoxFor(m => m.Child.Property)
#Html.LabelFor(m => m.Child.Property)
</div>
but I can't see how to update the text boxes with the child I get back in AJAX results.
The best I've been able to do is manually updating each field from the 'complete' method. But I've got about 30 more fields to add and it feels like the wrong approach.
How do I bind the edit boxes to the returned model without using partials and without refreshing the entire page?
I added Child as a property in the #model, and the TextFor appears to bind properly. But of course
#Model.Child = child
does not. So they never show any data.
This question was based on a misunderstanding on my part. At first I deleted the question when I realized my mistake. I'm reinstating it because I think it is an easy mistake for a noobie to fall into and once you do, the answer is hard to sort out.
The problem is that #model no longer exists once the page is rendered. There is no data binding going on behind the scenes as I thought there was.
Your options are
populating the elements manually. (this will need editing to fit your particular elements)
function DisplayMergeSection(data) {
for (var key of Object.keys(data)) {
DisplayElement(data, key, "#Clients_");
}
}
function DisplayElement(data, key, prefix) {
return;
var val = data[key];
var valString = data[key + "String"];
var element = $(prefix + key)[0];
if (element && element.type === 'text') {
if ((val || '').toString().indexOf("Date(") > -1) {
var dtStart = new Date(parseInt(val.substr(6)));
element.value = moment(dtStart).format('MM/DD/YYYY');
} else {
element.value = val;
}
} else if (element && element.type === 'checkbox') {
element.checked = val;
} else if (element && element.type === 'select-one') {
element.value = valString;
} else if (element && element.nodeName === 'DIV') {
if ((val || '').toString().indexOf("Date(") > -1) {
var dtStart = new Date(parseInt(val.substr(6)));
element.innerText = moment(dtStart).format('MM/DD/YYYY');
} else {
element.innerText = val;
}
}
}
create a bunch of observables with knockout and then set up data binding. This is a lot cleaner.
https://knockoutjs.com/documentation/json-data.html
set up a mapping with the knockout plugin.
https://knockoutjs.com/documentation/plugins-mapping.html
I need to clear up what may be a potential misunderstanding between me and the recaptcha library.
All of the examples that I have seen from searching around and finding YouTube videos of the v3 implementation have been attaching the recaptcha ready/execute functions within the context of a <form>... but I am trying to use reCaptcha outside the context of a <form> in the following way:
A user enters a name or a portion of their name into a search box
The frontend, after a delay of input or the enter key, gets names in the system which match this input (partial or full)
The results are loaded into a drop-down that auto-populates.
The user selects the appropriate name through either clicking or arrowing down, and this searches for specific data about that user.
If I can figure out how to get the reCaptcha token to take place on the name dropdown search, I will be able to extend that solution to the fourth point... but I am afraid that it may not be possible.
All code below is JavaScript:
Relevant Bits
Listener on search field:
$(".web-search-content").on("keyup", ".searchField", function(e) {
if (
e.keyCode != 40 &&
e.keyCode != 38 &&
e.keyCode != 13 &&
e.keyCode != 27
) {
delayCall($(this), 800, nameSearch, e);
} else if (e.keyCode == 27) {
$(".name-search-results").remove();
}
});
nameSearch function:
function nameSearch(obj, e) {
if (!e) {
e = window.event;
}
var container = $(".name-search-results");
if (
e.keyCode != 40 &&
e.keyCode != 38 &&
e.keyCode != 13 &&
e.keyCode != 27
) {
var field = obj;
var value = field.val();
if (value != undefined && value.length > 0) {
var captchaToken = "";
grecaptcha.execute('<REDACTED>', {action: 'webSearch/nameSearch'}).then(function(token) {
captchaToken = token;
console.log(captchaToken);
document.getElementById("g-recaptcha-response").value = token;
})
$.post("../webSearch/nameSearch", { query: value, captchaToken: captchaToken }, function(resp) {
showNameResults(resp);
});
} else {
container.remove();
}
}
}
Can someone confirm that reCaptcha v3 must be used within the context of a form, or can it handle events like this... and if so, how?
reCAPTCHA v3 is not tied to a form submission. From the docs:
reCAPTCHA v3 will never interrupt your users, so you can run it whenever you like without affecting conversion. reCAPTCHA works best when it has the most context about interactions with your site, which comes from seeing both legitimate and abusive behavior. For this reason, we recommend including reCAPTCHA verification on forms or actions as well as in the background of pages for analytics.
Note: You can execute reCAPTCHA as many times as you'd like with different actions on the same page.
So, you don't need to wait for the user to submit the form to run reCAPTCHA. Instead, you can make the call in the background for different actions across the site. Store the returned score and then check that on form submission to decide what action you want to take.
// Decide what score you want to allow as a pass
const threshold = 0.5;
// Set up a variable
let score = null;
// Call reCAPTCHA on page load
grecaptcha.ready(function () {
grecaptcha.execute('[redacted]', { action: 'pages/search' }).then(function (token) {
// Create an endpoint on your server to validate the token and return the score
fetch('/recaptcha-verify?action=pages/search&token=' + token).then(function (response) {
response.json().then(function (data) {
score = data.score;
});
});
});
});
// Later, e.g. in the form submission just check if the score is above the threshold
if (score >= threshold) {
// success
submitForm();
} else {
// score too low
doSomethingElse();
}
This is the approach used here:
https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php
https://github.com/google/recaptcha/blob/master/examples/recaptcha-v3-request-scores.php
I decided to break this onEdit(e) trigger function up into multiple functions, but when I did, the event objects (e) portion got "lost." After messing with it for a while, I finally got it to work again, but I don't think it's the most efficient solution.
Any suggestions, or is this good enough? Basically, I just added var e = e; and that made it work again.
function onEdit(e){
Logger.log(e);
if(e.range.getSheet().getName() == 'Estimate'){
var e = e;
Logger.log("Starting subCatDV...");
subCatDV(e);
Logger.log("Finished subCatDV!");
Logger.log("Starting itemDV...");
itemDV(e);
Logger.log("Finished itemDV!");
Logger.log("Starting subItemDV...");
subItemDV(e);
Logger.log("Finished subItemDV!");
}
if(e.range.getSheet().getName() == 'Items'){
subCatDV();
}
return;
}
Here is the function that didn't seem to be receiving the event objects
function subItemDV(e){
// Populate sub-item data validations
var estss = SpreadsheetApp.getActive().getSheetByName('Estimate');
var itemss = SpreadsheetApp.getActive().getSheetByName('Items');
var subItemDVss = SpreadsheetApp.getActive().getSheetByName('subItemDataValidations');
var activeCell = estss.getActiveCell();
Logger.log("I'm in subItemDV...");
Logger.log(e);
Logger.log(activeCell);
Logger.log("Checking sheet name...");
if(activeCell.getColumn() == 3 && activeCell.getRow() > 1){
if(e.range.getSheet().getName() == 'Items') return;
Logger.log("Not in 'Items' sheet! Moving on...");
activeCell.offset(0, 1).clearContent().clearDataValidations();
var subItem = subItemDVss.getRange(activeCell.getRow(),activeCell.getColumn(),itemss.getLastColumn()).getValues();
var subItemIndex = subItem[0].indexOf(activeCell.getValue()) + 2;
Logger.log("Checking subItemIndex...");
if(subItemIndex != 0){
var subItemValidationRange = subItemDVss.getRange(activeCell.getRow(),4,1,subItemDVss.getLastColumn());
var subItemValidationRule = SpreadsheetApp.newDataValidation().requireValueInRange(subItemValidationRange).build();
activeCell.offset(0, 1).setDataValidation(subItemValidationRule);
Logger.log("Finished checking subItemIndex...");
}
}
}
So as not to inflate discussion in comments: you can safely remove the var e = e assignment from the script, as it does not affect the problems that your updated version of the script solved:
e is an event object that is constructed as a response to trigger being fired. Since in your case the trigger is an onEdit(e) trigger, event object is undefined until an edit is made in the target Spreadsheet (please, note that script-triggered edits do not count);
Even if you called the function with a parameter (like doSomething(e)), in case you either do not access the parameter via the arguments object, or explicitly define it in a function declaration function doSomething(e), the event object won't be persisted;
Also, you might've missed the e binding in the last subCatDV() call + the if statement can be optimized (btw, don't use the equality comparison, use the identity comparison instead, it will save you debugging time in the future):
var name = e.range.getSheet().getName();
if(name === 'Estimate') {
doSomething(e);
}else if(name === 'Items') { //identity comparison ensures type match;
doSomethingElse(e);
}
Useful links
event object reference;
arguments object reference;
I am familiar with the Google Apps script DataValidation object. To get and set validation criteria. But how to tell programatically if a cell value is actually valid. So I can see the little red validation fail message in the spreadsheet but can the fact the cell is currently failing validation be picked up thru code?
I have tried to see if there is a cell property that tells you this but there is not. Also I looked for some sort of DataValidation "validate" method - i.e. test a value against validation rules, but nothing there either
Any ideas? Is this possible??
Specific answer to your question, there is no method within Google Apps Script that will return the validity of a Range such as .isValid(). As you state, you could reverse engineer a programatic one using Range.getDataValidations() and then parsing the results of that in order to validate again the values of a Range.getValues() call.
It's a good suggestion. I've added a feature request to the issue tracker -> Add a Star to vote it up.
I've created a workaround for this issue that works in a very ugly -technically said- and slightly undetermined way.
About the workaround:
It works based on the experience that the web browser implementation of catch() function allows to access thrown errors from the Google's JS code parts.
In case an invalid input into a cell is rejected by a validation rule then the system will display an error message that is catchable by the user written GAS. In order to make it work first the reject value has to be set on the specified cell then its vale has to be re-entered (modified) then -right after this- calling the getDataValidation() built in function allows the user to catch the necessary error.
Only single cells can be tested with this method as setCellValues() ignores any data validation restriction (as of today).
Disadvantages:
The validity won't be necessarily re-checked for this function:
it calls a cell validation function right after the value is inserted into the cell.
Therefore the result of this function might be faulty.
The code messes up the history as cells will be changed - in case they are
valid.
I've tested it successfully on both Firefox and Chromium.
function getCellValidity(cell) {
var origValidRule = cell.getDataValidation();
if (origValidRule == null || ! (cell.getNumRows() == cell.getNumColumns() == 1)) {
return null;
}
var cell_value = cell.getValue();
if (cell_value === '') return true; // empty cell is always valid
var is_valid = true;
var cell_formula = cell.getFormula();
// Storing and checking if cell validation is set to allow invalid input with a warning or reject it
var reject_invalid = ! origValidRule.getAllowInvalid();
// If invalid value is allowed (just warning), then changing validation to reject it
// IMPORTANT: this will not throw an error!
if (! reject_invalid) {
var rejectValidRule = origValidRule.copy().setAllowInvalid(false).build();
cell.setDataValidation(rejectValidRule);
}
// Re-entering value or formula into the cell itself
var cell_formula = cell.getFormula();
if (cell_formula !== '') {
cell.setFormula(cell_formula);
} else {
cell.setValue(cell_value);
}
try {
var tempValidRule = cell.getDataValidation();
} catch(e) {
// Exception: The data that you entered in cell XY violates the data validation rules set on this cell.
// where XY is the A1 style address of the cell
is_valid = false;
}
// Restoring original rule
if (rejectValidRule != null) {
cell.setDataValidation(origValidRule.copy().setAllowInvalid(true).build());
}
return is_valid;
}
I still recommend starring the above Google bug report opened by Jonathon.
I'm using this solution. Simple to learn and fast to use! You may need to adapt this code for your needs. Hope you enjoy
function test_corr(link,name) {
var ss = SpreadsheetApp.openByUrl(link).getSheetByName(name);
var values = ss.getRange(2,3,200,1).getValues();
var types = ss.getRange(2,3,200,1).getDataValidations()
var ans
for (var i = 0; i < types.length; i++) {
if (types[i][0] != null){
var type = types[i][0].getCriteriaType()
var dval_values = types[i][0].getCriteriaValues()
ans = false
if (type == "VALUE_IN_LIST") {
for (var j = 0; j < dval_values[0].length; j++) {
if (dval_values[0][j] == values[i][0]) { ans = true }
}
} else if (type == "NUMBER_BETWEEN") {
if (values[i][0] >= dval_values[0] && values[i][0] <= dval_values[1]) { ans = true }
} else if (type == "CHECKBOX") {
if (values[i][0] == "Да" || values[i][0] == "Нет") { ans = true }
}
if (!ans) { return false }
}
}
return true;
}
I have written a multiselect jQuery plugin that can be applied to a normal HTML select element.
However, this plugin will parse the select element and its options and then remove the select element from the DOM and insert a combination of divs and checkboxes instead.
I have created a custom binding handler in Knockout as follows:
ko.bindingHandlers.dropdownlist = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
// Retrieve the value accessor
var value = valueAccessor();
// Get the true value of the property
var unwrappedValue = ko.utils.unwrapObservable(value);
// Check if we have specified the value type of the DropDownList items. Defaults to "int"
var ddlValueType = allBindingsAccessor().dropDownListValueType ? allBindingsAccessor().dropDownListValueType : 'int';
// Check if we have specified the INIMultiSelect options otherwise we will use our defaults.
var elementOptions = allBindingsAccessor().iniMultiSelectOptions ? allBindingsAccessor().iniMultiSelectOptions :
{
multiple: false,
onItemSelectedChanged: function (control, item) {
var val = item.value;
if (ddlValueType === "int") {
value(parseInt(val));
}
else if (ddlValueType == "float") {
value(parseFloat(val));
} else {
value(val);
}
}
};
// Retrieve the attr: {} binding
var attribs = allBindingsAccessor().attr;
// Check if we specified the attr binding
if (attribs != null && attribs != undefined) {
// Check if we specified the attr ID binding
if (attribs.hasOwnProperty('id')) {
var id = attribs.id;
$(element).attr('id', id);
}
if (bindingContext.hasOwnProperty('$index')) {
var idx = bindingContext.$index();
$(element).attr('name', 'ddl' + idx);
}
}
if ($(element).attr('id') == undefined || $(element).attr('id') == '') {
var id = "ko_ddl_id_" + (ko.bindingHandlers['dropdownlist'].currentIndex);
$(element).attr('id', id);
}
if ($(element).attr('name') == undefined || $(element).attr('name') == '') {
var name = "ko_ddl_name_" + (ko.bindingHandlers['dropdownlist'].currentIndex);
$(element).attr('name', name);
}
var options = $('option', element);
$.each(options, function (index) {
if ($(this).val() == unwrappedValue) {
$(this).attr('selected', 'selected');
}
});
if (!$(element).hasClass('INIMultiSelect')) {
$(element).addClass('INIMultiSelect');
}
$(element).iniMultiSelect(elementOptions);
ko.bindingHandlers['dropdownlist'].currentIndex++;
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var unwrappedValue = ko.utils.unwrapObservable(valueAccessor());
var id = $(element).attr('id').replace(/\[/gm, '\\[').replace(/\]/gm, '\\]');
var iniMultiSelect = $('#' + id);
if (iniMultiSelect != null) {
iniMultiSelect.SetValue(unwrappedValue, true);
}
}};
ko.bindingHandlers.dropdownlist.currentIndex = 0;
This will transform the original HTML select element into my custom multiselect.
However, when the update function is called the first time, after the init, the "element" variable will still be the original select element, and not my wrapper div that holds my custom html together.
And after the page has been completely loaded and I change the value of the observable that I am binding to, the update function is not triggered at all!
Somehow I have a feeling that knockout no longer "knows" what to do because the original DOM element that I'm binding to is gone...
Any ideas what might be the issue here?
There is clean up code in Knockout that will dispose of the computed observables that are used to trigger bindings when it determines that the element is no longer part of the document.
You could potentially find a way to just hide the original element, or place the binding on a container of the original select (probably would be a good option), or reapply a binding to one of the new elements.
I ran into a similar problem today, and here's how I solved it. In my update handler, I added the following line:
$(element).attr("dummy-attribute", ko.unwrap(valueAccessor()));
This suffices to prevent the handler from being disposed-of by Knockout's garbage collector.
JSFiddle (broken): http://jsfiddle.net/padfv0u9/
JSFiddle (fixed): http://jsfiddle.net/padfv0u9/2/