Trigger input change on website using Angular with Chrome Extension content script - javascript

I'm creating a content-script which will populate
https://www.moneysupermarket.com/shop/car-insurance/questionset/#?step=highimpactquestions
with predefined data.
But I have problem with first step, the first field on the page, registration number.
I'm using jQuery inside my script, and I can set input's value to what I want using
$('input#regnumber').val(data['car_reg'])
But when I simulate click on find car button it seems like for website that field is empty, as 'Please enter a registration number' warning appears instead of showing car info.
I looked through the code, and saw that website uses angular, so I guess it has something to do with its binding.
I tried triggering change,keyup,input events on the $('input#regnumber') but it doesn't change anything.
EDIT:
Also tried with
$('input#regnumber').val(data['car_reg']).dispatchEvent(new Event("input", { bubbles: true }));
without success

AngularJS inputs use an ng-model directive to connect an input with the model:
<input id="regnumber" type="text" ng-model="formData.item" />
To set the model from outside the framework use:
var elem = $('input#regnumber');
var scope = angular.element(elem).scope();
scope.$apply(function() {
scope.formData.item = value;
});
For more information, see
AngularJS angular.element API Reference
AngularJS scope API Reference ($apply)
AngularJS Developer Guide - Scope (Integration with the Browser Event Loop)

You are getting validation error, because maybe the value of data['car_reg'] is undefined. If the data is an array of objects, then use like data[0].car_reg. Use console to verify the value.

I found a way finally.. this is what I did in my background script for the tab where this page is loaded:
setTimeout(function() {
chrome.tabs.executeScript(tab.id, { 'file': '/jquery-2.2.4.min.js', 'runAt': 'document_start' })
chrome.tabs.executeScript(tab.id, { 'file': '/angular.min.js' })
setTimeout(function() {
chrome.tabs.executeScript(tab.id, { 'code': 'angular.reloadWithDebugInfo();' });
setTimeout(function() {
chrome.tabs.executeScript(tab.id, { 'file': '/jquery-2.2.4.min.js' });
chrome.tabs.executeScript(tab.id, { 'file': '/formfiller.js' })
}, 5000);
}, 5000);
}, 8000)
And in my formfiller.js I do this in order to fill that field:
var scrpt = document.createElement('script');
scrpt.innerText = "var scp = angular.element($('#regnumber')).scope();scp.regnumber = '12345';scp.$apply(); $('.car-registration__button').click();"
document.head.appendChild(scrpt);
scrpt.onload = function() {
scrpt.parentNode.removeChild(scrpt);
};

Related

Canada Post AddressComplete on "populate" not working

I'm trying to use Canada Post's Address Complete on my form as such
var fields = [
{ element: "street_address", field: "Line1" },
{ element: "city_address", field: "City", mode: pca.fieldMode.POPULATE },
{ element: "postal_code", field: "PostalCode", mode: pca.fieldMode.POPULATE },
{ element: "country", field: "CountryName", mode: pca.fieldMode.COUNTRY }
],
options = {key: KEY},
control = new pca.Address(fields, options);
addressComplete.listen('load', function(control) {
control.listen("populate", function (address) {
if(address.ProvinceCode == "ON"){
console.log("ONTARIO");
document.getElementById('province').selectedIndex = 2;
}
else if(address.ProvinceCode == "QC"){
document.getElementById('province').selectedIndex = 3;
}
});
});
I'm able to search for an address and have some fields auto populate. The Province on my form is a dropdown which is where I want to use the listener as suggested in the website, but it doesn't work? Could someone please let me know what I'm doing wrong?
I tried playing with the API and I couldn't get any events to fire on the addressComplete object but the ready event. However, since we have all ready constructed a control instance, I just removed the load listener and attached the populate event handler directly to the control object we constructed. This seemed to work.
//addressComplete.listen('load', function (control) {
control.listen('populate', function (address) {
// TODO: Handle populated address here.
});
//});
I got error - Uncaught ReferenceError: control is not defined
Once the Canada Post JavaScript is loaded, then the control instance is created - addressComplete.controls[0]
To listen to populate event of the control:
addressComplete.controls[0].listen("populate", function (address) {
// TODO: Handle populated address here.
});
load() and reload() apis are also available.
addressComplete.controls[0].load();
addressComplete.controls[0].reload();
// destroy();
// load();

Bootstrap-confirmation not respecting options

Just adding the bootstrap-confirmation extension for Bootstrap popover to some buttons on a project. I'm having issues with the options not being respected. I'm trying to get the popups to work as singletons and dismiss when the user clicks outside of them singleton and data-popout options, respectively - both set to true. I'm also not seeing any of my defined callback behavior happening.
I defined the options both in the HTML tags and in a function and neither works. Still getting multiple boxes and they don't dismiss as expected.
My JS is loaded after all other libraries and is in my custom.js file in my footer.
JS is as follows:
$(function() {
$('body').confirmation({
selector: '[data-toggle="confirmation"]',
singleton: true,
popout: true
});
$('.confirmation-callback').confirmation({
onConfirm: function() { alert('confirm') },
onCancel: function() { alert('cancel') }
});
});
An example of the box implemented on a button in my HTML is the following:
<a class="btn btn-danger" data-toggle="confirmation" data-singleton="true" data-popout="true"><em class="fa fa-trash"></em></a>
Any pointers would be appreciated. I even changed the default options in the bootstrap-confirmation.js file itself to what I want and still no luck.
Turns out I needed to rearrange a couple things to get this to work. I've left in the last_clicked_id etc stuff as I needed to add that to get the id value of what I'd just clicked.
// Product removal popup logic
var last_clicked_id = null;
var last_clicked_product = null;
$('.btn.btn-danger.btn-confirm').click(function () {
last_clicked_id = $(this).data("id");
last_clicked_product = $(this).data("product");
});
$('.btn.btn-danger.btn-confirm').confirmation({
singleton: true,
popout: true,
onConfirm: function () {
alert("DEBUG: Delete confirmed for id : " + last_clicked_product);
// TODO: Add AJAX to wipe entry and refresh page
},
onCancel: function () {
alert("DEBUG: Delete canceled for id : " + last_clicked_product);
}
});
I was a step ahead of myself with the callback logic which was not getting executed. Fixed by simply adding it to onConfirm: and onCancel: key values in the .confirmation() function. A bit of a RTFM moment there but this was unfortunately not very clear in the documentation.

Why my applyBindings doesn't work? Knockout

Hello I am trying simply to create input and iframe and when I paste the YouTube link the iframe should change with the new src. I have done this so far
<div class="heading">id <input data-bind="text: youtubeLink"/></div>
<iframe id="player" type="text/html" width="444" height="250" frameborder="0" data-bind="attr: { src: linkEmbed }"></iframe>
And in the script:
function MyViewModel() {
this.youtubeLink = ko.observable('https://www.youtube.com/watch?v=4UNkmlCKw9M');
this.linkEmbed = ko.pureComputed({
read: function () {
var extract = this.youtubeLink().replace("/watch?v=", "/embed/");
console.log(extract)
return extract;
},
write: function (value) {
this.youtubeLink();
},
owner: this
});
}
ko.applyBindings(MyViewModel());
This works exactly as I want but the video wont change if I paste another link in the input.
I am using this from knockout documentation: http://knockoutjs.com/documentation/computed-writable.html
You have several problems:
You don't call new on your model, but you wrote it as a constructor
You use text binding instead of value binding for your input
Your computed's write doesn't assign, but you don't need it anyway
Once you correct those, it works.
function MyViewModel() {
var model = {};
model.youtubeLink = ko.observable('https://www.youtube.com/watch?v=4UNkmlCKw9M');
model.linkEmbed = ko.pureComputed(function () {
var result = model.youtubeLink().replace("/watch?v=", "/embed/")
return result;
});
return model;
}
ko.applyBindings(MyViewModel());
http://jsfiddle.net/ueoob7ne/2/
TLDR: jQuery hides knockout bind errors.
Another thing that breaks it....
jQuery is known to catch exceptions and hide them. I had to step through knockout-debug.js AND THEN jquery.js until i got to a part that looks like this (around line 3600)
// Only normal processors (resolve) catch and reject exceptions
process = special ?
mightThrow :
function() {
try {
mightThrow();
} catch ( e ) {
wouldn't you know it... I put a watch on (e) an here was what I found hidden in there:
Error: Unable to process binding "text: function(){return ko.toJSON(vm.model(),null,2) }"
Message: Multiple bindings (if and text) are trying to control descendant bindings of the same element

Alfresco Share client-side synchronous Form validation

I have an Alfresco Share client-side Form in a registration scenario and I want to validate if the chosen username is taken.
Can I add a Alfresco forms validator in a synchronous way?
So far my form goes something like this:
this.widgets.saveForm = new Alfresco.forms.Form(this.id + "-form");
this.widgets.saveForm.addValidation(Dom.get(this.id + "-username"), this.validateUsername, me, "blur", this.msg("validation-hint.username-taken"));
But this.validateUsername needs to make an Alfresco.util.Ajax.request to a repo-side web service which checks availability for the chosen username. So basically, by the time the response is here, my original validateUsername method is long-finished, returning false (likely).
My progress so far is to disable the submit button (that is why I am passing "me" to the validation handler), and have success/failure callbacks enable it if the username is fine.
What would be a decent way to make a synchronous validator?
A side question would be if there is a better way to set the scope of the validation handler to "this", as opposed to passing me (this one works too, this is just to make sure I am not missing a better way).
This is a snippet of the create-site form. It does the same and checks if the shortname is already present and shows an error if it's taken.
createSiteForm.doBeforeFormSubmit =
{
fn: function()
{
var formEl = Dom.get(this.id + "-form");
formEl.attributes.action.nodeValue = Alfresco.constants.URL_SERVICECONTEXT + "modules/create-site";
this.widgets.okButton.set("disabled", true);
this.widgets.cancelButton.set("disabled", true);
// Site access
var siteVisibility = "PUBLIC";
if (this.widgets.isPublic.checked)
{
if (this.widgets.isModerated.checked)
{
siteVisibility = "MODERATED";
}
}
else
{
siteVisibility = "PRIVATE";
}
this.widgets.siteVisibility.value = siteVisibility;
this.widgets.panel.hide();
this.widgets.feedbackMessage = Alfresco.util.PopupManager.displayMessage(
{
text: Alfresco.util.message("message.creating", this.name),
spanClass: "wait",
displayTime: 0
});
},
obj: null,
scope: this
};
// Submit as an ajax submit (not leave the page), in json format
createSiteForm.setAJAXSubmit(true,
{
successCallback:
{
fn: this.onCreateSiteSuccess,
scope: this
},
failureCallback:
{
fn: this.onCreateSiteFailure,
scope: this
}
});
So this is the client side JavaScript file create-site.js which does the ajax call just before the submit.
My workaround solution is this:
Add a custom validation to the form, and pass the "var me = this" for the validator function to get the scope.
this.widgets.saveForm.addValidation(Dom.get(this.id + "-username"), this.validateUsername, me, "blur", this.msg("validation-hint.username-taken"));
Add a validation handler function which will a. disable the submit button and set the "checking" CSS class to the appropriate form for the user to have feedback. Code snip:
validateUsername: function Reg_validateUsername(el, me) {
var val = this.fieldId.value;
var username = trim(val);
me.widgets.registerButton.set("disabled", true);
Dom.addClass(me.id + "-username-img", "checking");
Dom.removeClass(me.id + "-username-img", "hidden");
Alfresco.util.Ajax.request({
url: "/alfresco/service/slingshot/register/username-available?username=" + username
, method: "GET"
, successCallback: {
fn: me.onUsernameAvailable
, scope: me
}
, failureCallback: {
fn: me.onUsernameTaken
, scope: me
}
});
Write a backend webscript username-available.get that will return success only if the username is free (and valid, even though I have another validator for that).
onUsernameAvailable will set the submit button to enabled again and run validation again (because of other fields) and change the CSS class to "valid".
onUsernameTaken will set the CSS class to "taken".
That's how I ended up doing this.

How to place a jQuery snippet into a global file

I have a JavaScript file here http://www.problemio.com/js/problemio.js and I am trying to place some jQuery code into it that looks like this:
$(document).ready(function()
{
queue = new Object;
queue.login = false;
var $dialog = $('#loginpopup')
.dialog({
autoOpen: false,
title: 'Login Dialog'
});
var $problemId = $('#theProblemId', '#loginpopup');
$("#newprofile").click(function ()
{
$("#login_div").hide();
$("#newprofileform").show();
});
// Called right away after someone clicks on the vote up link
$('.vote_up').click(function()
{
var problem_id = $(this).attr("data-problem_id");
queue.voteUp = $(this).attr('problem_id');
voteUp(problem_id);
//Return false to prevent page navigation
return false;
});
var voteUp = function(problem_id)
{
alert ("In vote up function, problem_id: " + problem_id );
queue.voteUp = problem_id;
var dataString = 'problem_id=' + problem_id + '&vote=+';
if ( queue.login = false)
{
// Call the ajax to try to log in...or the dialog box to log in. requireLogin()
}
else
{
// The person is actually logged in so lets have him vote
$.ajax({
type: "POST",
url: "/problems/vote.php",
dataType: "json",
data: dataString,
success: function(data)
{
alert ("vote success, data: " + data);
// Try to update the vote count on the page
//$('p').each(function()
//{
//on each paragraph in the page:
// $(this).find('span').each()
// {
//find each span within the paragraph being iterated over
// }
//}
},
error : function(data)
{
alert ("vote error");
errorMessage = data.responseText;
if ( errorMessage == "not_logged_in" )
{
//set the current problem id to the one within the dialog
$problemId.val(problem_id);
// Try to create the popup that asks user to log in.
$dialog.dialog('open');
alert ("after dialog was open");
// prevent the default action, e.g., following a link
return false;
}
else
{
alert ("not");
}
} // End of error case
}
}); // Closing AJAX call.
};
$('.vote_down').click(function()
{
alert("down");
problem_id = $(this).attr("data-problem_id");
var dataString = 'problem_id='+ problem_id + '&vote=-';
//Return false to prevent page navigation
return false;
});
$('#loginButton', '#loginpopup').click(function()
{
alert("in login button fnction");
$.ajax({
url:'url to do the login',
success:function() {
//now call cote up
voteUp($problemId.val());
}
});
});
});
</script>
There are two reasons why I am trying to do that:
1) I am guessing this is just good practice (hopefully it will be easier to keep track of my global variables, etc.
2) More importantly, I am trying to call the voteUp(someId) function in the original code from the problemio.js file, and I am getting an error that it is an undefined function, so I figured I'd have better luck calling that function if it was in a global scope. Am I correct in my approach?
So can I just copy/paste the code I placed into this question into the problemio.js file, or do I have to remove certain parts of it like the opening/closing tags? What about the document.ready() function? Should I just have one of those in the global file? Or should I have multiple of them and that won't hurt?
Thanks!!
1) I am guessing this is just good practice (hopefully it will be
easier to keep track of my global variables, etc.
Yes and no, you now have your 'global' variables in one spot but the chances that you're going to collide with 'Global' variables (ie those defined by the browser) have increased 100% :)
For example say you decided to have a variable called location, as soon as you give that variable a value the browser decides to fly off to another URL because location is a reserved word for redirecting.
The solution to this is to use namespacing, as described here
2) More importantly, I am trying to call the voteUp(someId) function
in the original code from the problemio.js file, and I am getting an
error that it is an undefined function, so I figured I'd have better
luck calling that function if it was in a global scope. Am I correct
in my approach?
Here's an example using namespacing that will call the voteUp function:
(function($) {
var myApp = {};
$('.vote_up').click(function(e) {
e.preventDefault();
myApp.voteUp();
});
myApp.voteUp = function() {
console.log("vote!");
}
})(jQuery);
What about the document.ready() function? Should I just have one of
those in the global file? Or should I have multiple of them and that
won't hurt?
You can have as many document.ready listeners as you need, you are not overriding document.ready you are listening for that event to fire and then defining what will happen. You could even have them in separate javascript files.
Be sure your page is finding the jquery file BEFORE this file is included in the page. If jquery is not there first you will get function not defined. Otherwise, you might have other things conflicting with your jquery, I would look into jquery noConflict.
var j = jQuery.noConflict();
as seen here:
http://api.jquery.com/jQuery.noConflict/
Happy haxin
_wryteowl
Extending what KreeK has already provided: there's no need to define your "myApp" within the document ready function. Without testing, I don't know off the top of my head if doing so is a potential source for scope issues. However, I CAN say that the pattern below will not have scope problems. If this doesn't work, the undefined is possibly a script-loading issue (loading in the right order, for example) rather than scope.
var myApp = myApp || {}; // just adds extra insurance, making sure "myApp" isn't taken
myApp.voteUp = function() {
console.log("vote!");
}
$(function() { // or whatever syntax you prefer for document ready
$('.vote_up').click(function(e) {
e.preventDefault();
myApp.voteUp();
});
});

Categories