Shopify: Checkout ajax validations? - ajax

I need to add ajax validations in the checkout page when the users click on the Continue button in each checkout step. Does anyone know if this is possible?
I need to send the Cart object, Address, items, etc. The idea is, the users are unable to continue, if they fail the validation.

You can only do this if you are on Shopify Plus. You cannot inject Javascript into checkout for the very good reason that you'd probably break it! It is a finely tuned machine, so the most you can do usually is change some css classes.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
If you have Shopify Plus, then you can insert your js form validation.
Also you have to replace the given input id with you custom id. After which, you can insert, check and get data from input fields
Below is script for phone number validation
$("#checkout_shipping_address_phone").attr("id","checkout_shipping_address_phone_clone");
document.getElementById('checkout_shipping_address_phone_clone').setAttribute('maxlength','10');
document.getElementById('checkout_shipping_address_phone_clone').setAttribute('max-length','10');
// document.getElementById('checkout_shipping_address_phone').setAttribute('type','number');
// document.getElementById('checkout_shipping_address_phone').setAttribute('minlength','10');
// document.getElementById('checkout_shipping_address_phone').setAttribute('data-minlength','10');
let mobileBtn = document.getElementById('checkout_shipping_address_phone_clone');
mobileBtn.removeAttribute('aria-required');
mobileBtn.removeAttribute('data-phone-formatter-country-select');
mobileBtn.removeAttribute('data-phone-formatter');
mobileBtn.removeAttribute('autocomplete');
mobileBtn.setAttribute("onkeypress","return isNumber(event)");
mobileBtn.type='text';
console.log("aaa");
function isNumber(evt){
evt = (evt) ? evt : window.event;
var charCode = (evt.which) ? evt.which : evt.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
return false;
}
return true;
}
$("body").on("click","#continue_button",function(){
var hh = $('#checkout_shipping_address_phone_clone').val();
if(hh.length==10){
console.log('dbcjhsdvjhdsvhjsjh');
$("#error_msgehh").remove();
$('#continue_button').attr('type','submit');
$('#continue_button').css('opacity','1');
}
else{
console.log(hh.length);
$('#continue_button').css('opacity','.5');
$("#error_msgehh").remove();
$('#checkout_shipping_address_phone_clone').parent().after().append('<p id="error_msgehh" style="color:red;">Enter 10 Digit Phone Number</p>');
$('#continue_button').attr('type','button');
}
});
$('#checkout_shipping_address_phone_clone').attr('maxlength','10');
$('#checkout_shipping_address_phone_clone').attr('max-length','10');
$("body").on("keyup mouseenter mouseleave","#checkout_shipping_address_phone_clone",function(){
var hh = $(this).val();
const regexExp = /^[6-9]$/gi;
let firstChar = regexExp.test(hh[0]); // dont allow 0,1,2,3,4,5 digits at first place
if(!firstChar){
var v ='';
$('#checkout_shipping_address_phone_clone').val(v);
}
});
$("body").on("keyup mouseenter mouseleave",".field__input-wrapper input",function(){
var hh = $('#checkout_shipping_address_phone_clone').val();
console.log('hover');
const regexExp = /^[6-9]$/gi;
let firstChar = regexExp.test(hh[0]); // dont allow 0,1,2,3,4,5 digits at first place
if(!firstChar){
var v ='';
$('#checkout_shipping_address_phone_clone').val(v);
}
});
// console.log("bbbaaa");
// });

Related

Using reCaptcha3 Without Forms?

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

code highlighting apex (Firefox 31)

The Oracle Application Express code editor is just plain back text on white background. No Code highlighting. Also I can't press "tab" without the textfield loosing focus.
I am using firefox 31 (can't upgrade, rescricted by Admin at work here) Also I can't install plugins. I know you can change css on specific sites using a special folder in firefox ("chrome"-folder / userContent.css). I already used this to change die default size of the textfield, because it was frickin small everytime I opened the edit page.
So do you know any framework or script I can use in Apex ? (I could copy that shit to jsfiddle.net every time but that sucks
(I also found the scratchpad in Firefox, which can run js and jquery. Does that help ?)
[SOLVED]
since you can't use
<script src = "">
etc. in plain js, I had to use loadScript. For css files it was even more complicated, but I got it all working.
This is my code, I run it in scratchpad (firefox). It uses ACE to change a div to an editor with highlighting. When clicking apply I revert the editor-changes in the DOM but keep the text/code.
// Load Ace js
loadScript("http://cdnjs.cloudflare.com/ajax/libs/ace/1.1.01/ace.js", function(){
//initialization code
});
// Load Ace css
var cssId = 'myCss'; // you could encode the css path itself to generate id..
if (!document.getElementById(cssId)){
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/css/bootstrap.min.css';
link.media = 'all';
head.appendChild(link);
}
// change textarea to div
var editorRegion = document.getElementById('F4000_P4651_PLUG_SOURCE_fieldset');
editorRegion.innerHTML = editorRegion.innerHTML.replace("textarea","div");
// run ACE
highlight();
// Modify the apply Button in Apex to first revert ACE-Editor to normal, then do the usual apply.
var applyChanges = document.getElementById('B3456326662');
applyChanges.setAttribute("onclick","modifiedApply()");
function modifiedApply(){
close();
setTimeout(normalApply, 500);
}
function normalApply(){
javascript:apex.submit('Apply_Changes');
}
// Revert ACE-Changes, but keep changed text/code.
function close(){
var value = editor.getValue();
editor.destroy();
var oldDiv = editor.container;
var newDiv = oldDiv.cloneNode(false);
newDiv.textContent = value;
oldDiv.parentNode.replaceChild(newDiv, oldDiv);
newDiv.outerHTML = newDiv.outerHTML.replace("div","textarea");
var old_new_old = document.getElementById('F4000_P4651_PLUG_SOURCE');
old_new_old.textContent = old_new_old.textContent.substring(0, old_new_old.textContent.length - 6);
}
var editor;
function highlight() {
editor = ace.edit("F4000_P4651_PLUG_SOURCE");
editor.setTheme("ace/theme/monokai");
editor.getSession().setUseWorker(false);
editor.getSession().setMode("ace/mode/javascript");
document.getElementsByClassName('ace_print-margin')[0].setAttribute("style","left:1000px");
}
function loadScript(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}

jqGrid - Inline edit - Detect dirty / changed cells

is there an example of using jqgrid's getChangedCells
method to determine if data has changed?
I grepped getChangedCells in the downloadable demos for
jqgrid, and could only find the function definition, not
example usages of getChangedCells.
What I want to do is save the edits that a user's
made if the user clicks on another row. But, I only
want to submit the save if the row is dirty.
Thanks in advance,
--Nate
There are no safe dirty flag on the row. You can use the fact that at the beginning of row editing (at the start of the inline editing mode) the method editRow add editable="1" attribute to the grid row (<tr> element). Later the methods saveRow and restoreRow changes the attribute value to editable="0". So the rows of the current page which was at least once in the inline editing mode will have the editable attribute. If the id of the table element is "list" you can find the edited rows with
$("#list tr[editable]")
The ids of the elements of the set are the rowids of the rows.
If you use paging in the grid you should be careful and save the ids of the edited rows on the current page before the changing of the page. The onPaging event would help you here.
In my opinion the best and the most safe way to do what you need is to use aftersavefunc parameter of the editRow or saveRow methods (probably you use directly only editRow). Inside of your aftersavefunc function you can save the id of the modified row in an array/map. This will solve your problem and will safe work.
Finally, I managed to bring a piece of code to detect what we want ;)
Hopefully any jqgrid gurus there (like Oleg), have enough time to review this code and improve it.
The example code will work for detect data changed in a grid with an editable field named "name". If you want to check for changed data in more columns, you have to add the variables after_edit and before_edit asociated with that columns.
To get the previous cell data inside the onSelectRow function, I don't used the getCell method because in the documentation says in red:
Do not use this method when you editing the row or
cell. This will return the cell content and not the
actuall value of the input element
By disgrace I could check that the documentation was right :(.
However the getCell function works properly with the current cell data.
And here is the code:
// Declare variables used for inline edit functionality.
var last_selected;
var before_edit_value;
var after_edit_value;
$('#grid-id').jqGrid({
...
onSelectRow: function(row_id){
if(row_id && row_id !== last_selected) {
/*
* Determine if the value was changed, if not there is no need to save to server.
*/
if (typeof(last_selected) != 'undefined') {
after_edit_value = $('#grid-id tr#' + last_selected + ' .name_column input').val();
}
if (before_edit_value != after_edit_value) {
/*
* Save row.
*/
$('#grid-id').jqGrid(
'saveRow',
last_selected,
function(response){
/* SuccessFunction: Do something with the server response */
return true;
},
'http://url.to.server-side.script.com/server-side-script.php',
{
additional_data: 'example: additional string',
});
}
else {
/*
* Restore the row.
*/
$('#grid-id').jqGrid('restoreRow', last_selected);
}
before_edit_value = $('#grid-id').jqGrid('getCell', row_id, 'name');
}
last_selected = row_id;
/*
* Edit row.
*/
$('#grid-id').jqGrid(
'editRow',
row_id,
true,
function() {/* OnEditFunction */},
function(response) {
/* SuccessFunction: Do something with the server response */
return true;
},
'http://url.to.server-side.script.com/server-side-script.php',
{
additional_data: 'example: additional string',
});
},
...
});
In one of my projects I did the following: before editing the row I remember row data in global variable and after editing is done just check if row data was changed. Something like this (edit mode activated by double click):
var beforeEditData;
function onGridDblClickRow(id) {
if (isRowEditable(id)) {
beforeEditData = grid.getRowData(id);
grid.editRow(id, true, null, null, 'clientArray', null, onRowAfterEdit);
...
}
}
function onRowAfterEdit(row) {
var data = grid.getRowData(row);
if (!isDataChanged(beforeEditData, data)) {
return; // No changes
}
... // Save data here
}
function isDataChanged(before, after){
... // Allows tricky logic for dirty data, e.g. one may trim spaces etc.
}
Using MVC4 and JQuery this is what I did
In the View
<script type="text/javascript">
var $grid = $("#Grid");
var lastSelection;
var datachanged = false;
function gridInitialised() {
var headers = $('th>div>:input');
for (var h = 0; h < headers.length; headers[h++].onclick = (function () { if (datachanged) { $grid.saveRow(lastSelection); datachanged = false; } }));
}
function editRow(id) {
if (id && id !== lastSelection) {
if (datachanged) { $grid.saveRow(lastSelection); datachanged = false; }
$grid.restoreRow(lastSelection);
$grid.editRow(id, true);
var inputs = $('#'+id+'>td>:input[class="editable"]');
for (var i = 0; i < inputs.length; inputs[i++].onchange = (function () { datachanged = true; }));
lastSelection = id;
}
}
</script>
#Html.Trirand().JQGrid(Model.Grid, "Grid")
in the Model
Grid.ClientSideEvents.RowSelect = "editRow";
Grid.ClientSideEvents.GridInitialized = "gridInitialised";
The gridInitialised code is to handle changes to the search filter.
Dave
As Oleg mentioned 5 (wow) years ago - I used the saveRow function and passed the flag as extraparam.
something like this, assuming your "model" or a hidden column IsDirty in my case:
onSelectRow: function(id) {
if (id && id !== lastgridsel) {
$("#myGrid").saveRow(lastgridsel, false, "clientArray", { IsDirty: "True" });
$("#myGrid").editRow(id, true, null, null, "clientArray");
lastgridsel = id;
}
},
and then loop through the rows on Save click (external button in my case), something along the lines of:
$("#gridSaveBtn").on("click", function() {
var batch = new Array();
var dataIds = $("#myGrid").jqGrid("getDataIDs");
for (var i = 0; i < dataIds.length; i++) {
try {
$("#myGrid").jqGrid("saveRow", dataIds[i], false, "clientArray");
//get row data
var data = $("#myGrid").jqGrid("getRowData", dataIds[i]);
if (data["IsDirty"] === "True") {
batch.push(data);
}
} catch (ex) {
alert(ex.Message);
$("#myGrid").jqGrid("restoreRow", dataIds[i]);
}
}
});

Magento Enterprise Tabs - How to select specific tab in link?

I am trying to link to a specific tab in Magento Enterprise. It seems that all of the answers I've found don't apply well to their method. I just need a link to the page to also pull up a specific tab. This is the code they use:
Enterprise.Tabs = Class.create();
Object.extend(Enterprise.Tabs.prototype, {
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
this.activeTab = this.tabs.first();
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
},
handleTabClick: function (evt) {
this.activeTab = Event.findElement(evt, 'dt');
this.select();
},
select: function () {
for (var i = 0, l = this.tabs.length; i < l; i ++) {
if (this.tabs[i] == this.activeTab) {
this.tabs[i].addClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 2;
/*this.tabs[i].next('dd').show();*/
new Effect.Appear (this.tabs[i].next('dd'), { duration:0.5 });
this.tabs[i].parentNode.style.height=this.tabs[i].next('dd').getHeight() + 15 + 'px';
} else {
this.tabs[i].removeClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 1 - i;
this.tabs[i].next('dd').hide();
}
}
}
});
Anyone have an idea?
I would consider modifying how the class starts up.
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
// change starts here //
var hashTab = $(window.location.hash.slice(1));
this.activeTab = ( this.tabs.include(hashTab) ? hashTab : this.tabs.first());
// change ends here //
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
}
Here, I have only changed how the initial tab is chosen. It checks for an URL fragment which is commonly known as a hash, if that identifies one of the tabs it is preselected. As a bonus the browser will also scroll to that element if possible.
Then you only need to append the tab's ID to the URL. For example you might generate the URL by;
$productUrl = Mage::getUrl('catalog/product/view', array(
'id' => $productId,
'_fragment' => 'tab_id',
));
If you've recently migrated from an earlier Magento release, e.g. from Enterprise 1.11 to Enterprise 1.12, make sure the javascript in /template/catalog/product/view.phtml
right after the foreach that generates the tabs gets updated to the 1.12 version:
<script type="text/javascript">
var collateralTabs = new Enterprise.Tabs('collateral-tabs');
Event.observe(window, 'load', function() {
collateralTabs.select();
});
</script>
surfimp's VERY helpful suggestions did not produce the desired opening of the closed tab otherwise. Once this updated javascript was added, clicking on a link to read Review or Add Your Review on the product page, jumped to the Reviews tab, even if the tab had been hidden.
Similar to Zifius' answer, you can modify the initialize function to just take another argument which will be the active tab.
Event.observe(window, 'load', function() {
new Enterprise.Tabs('collateral-tabs', $('tab_review'));
});
and then in the scripts.js (or wherever this class may exist for you)
initialize: function (container, el) {
...
this.activeTab = el;
...
}
Use whatever logic in the template you like to set 'el' to the desired value.
The reason I did it this way is because when I used Zifius' method, the desired tab would be the active tab, but the default tab's content was still displayed.
Had the same task yesterday and as I don't know about prototype much I solved it by adding another method:
selectTab: function (element) {
this.activeTab = element;
this.select();
},
Usage:
var Tabs = new Enterprise.Tabs('collateral-tabs');
Tabs.selectTab($('tabId'));
Would like to know if it's a correct approach

Change text in Div using jQuery, MetaData and Map highlights

I'm a newbie to jQuery and I have a map with a highlight plugin, when mouse over an area I want to change the text in a div with an ID and the text I will get it from the area attribute Alt="some text"
Here is the code that used for area loops, I'm pretty sure I can add a small function here but I couldn't figure it out.
//map
clicks$(".tabs area").click(function(){
//areas loop:
$(".tabs area").each(function(){
var d = $(this).data('maphilight') || {};
if(d.alwaysOn == true){
d.alwaysOn = false;
}
});
var data = $(this).data('maphilight') || {};
data.alwaysOn = true;
$(this).data('maphilight', data).trigger('alwaysOn.maphilight');
if ($(this).hasClass("current") == false)
{
var thisTarget = $(this).attr("href");
$(this).parents(".tabs").find('area.current').removeClass('current');
$(this).addClass('current');
$(this).parents(".tabs").nextAll(".tab-content").children(":visible").fadeOut(1, function() {
$(thisTarget).fadeIn("fast");
});
}
return false;
});
Any help or suggestions on how I can get this done would be highly appreciated.
I'm not familiar with the highlights plugin, but I think you just wanna add a mouseover event to each area like so (you would place this before/after your .click declaration):
$(".tabs area").mouseover(function() {
var alt_text = $(this).attr('alt');
$("#YOUR_TEXT_DIV_ID").html(alt_text);
}).mouseout(function() {
//do something on mouseout
});

Resources