Scopes/Closures in Javascript - How to update global variable? - javascript

So I've been puzzling about this and just can't figure out how to fix this.
I've a nested function that loops through an array of objects and scrapes the social links of each object's URL.
After that, I'd like to update each object by including { social: [array of social urls] }. However, I get { social: [] } instead.
I've tried placing the contacts[i].social = results on other sections of this, but I get an "object not found" error.
Help would be much appreciated...
var contacts = [
{ title: 'Healthy breakfast: Quick, flexible options to grab at home - Mayo Clinic',
url: 'http://www.mayoclinic.org/healthy-living/nutrition-and-healthy-eating/in-depth/food-and-nutrition/art-20048294' },
{ title: 'Healthy Breakfast Ideas from Dr. Weil\'s Facebook Readers',
url: 'http://www.drweil.com/drw/u/ART03113/Healthy-Breakfast-Ideas-from-Facebook-Readers.html' },
{ title: '8 Healthy Breakfast Ideas - Prevention.com',
url: 'http://www.prevention.com/food/healthy-eating-tips/8-healthy-breakfast-ideas' },
{ title: 'Quick & Easy Healthy Breakfast Ideas! - YouTube',
url: 'http://www.youtube.com/watch?v=mD4YSD8LDiQ' },
{ title: 'The Best Foods to Eat for Breakfast - Health.com',
url: 'http://www.health.com/health/gallery/0,,20676415,00.html' },
{ title: 'Healthy Breakfast Recipes - Secrets To Cooking Healthier - YouTube',
url: 'http://www.youtube.com/watch?v=7jH0xe1XKxI' },
{ title: 'Healthy Breakfast Ideas You Can Make the Night Before - FitSugar',
url: 'http://www.fitsugar.com/Healthy-Breakfast-Ideas-You-Can-Make-Night-Before-20048633' },
{ title: '10 Easy, 5-Minute Breakfast Ideas - Diet and ... - Everyday Health',
url: 'http://www.everydayhealth.com/diet-and-nutrition-pictures/easy-5-minute-breakfast-ideas.aspx' },
{ title: 'Healthy Breakfast Ideas for Kids | Parenting - Parenting.com',
url: 'http://www.parenting.com/gallery/healthy-breakfast-ideas-kids' },
{ title: 'Fruits & Veggies More Matters : Healthy Breakfast Ideas : Health ...',
url: 'http://www.fruitsandveggiesmorematters.org/healthy-breakfast-ideas' }
];
scraper(contacts);
// loops through contacts database and scrapes requested information
function scraper(contacts) {
// Adds the domain of each contact
for(var i=0;i<contacts.length;i++){
contacts[i].domain = contacts[i].url.split(/\//, 3).join().replace(/,/g, '/');
};
//
for(var i=0;i<contacts.length;i++){
var homepage = contacts[i].domain;
var results = [];
function socialScrape(homepage) {
request(homepage, function(err, resp, html) {
var $ = cheerio.load(html);
if(!err && resp.statusCode == 200) {
$('a').each(function(i, el){
var a = $(el).attr('href');
for(var key in socialURLS){
if(socialURLS[key].test(a) && results.indexOf(a) < 0){
results.push(a);
}
}
});
} else { console.log(err); }
})
}
contacts[i].social = results;
socialScrape(homepage);
}
console.log(contacts);
}

Your first issue is that your request call is asynchronous and hasn't yet returned by the time contacts[i].social = results is executed, so contacts[i].results is getting assigned an empty array, []. (Variations of this issue are posted on SO multiple times every day, a good explanation of the problem can be found here: How do I return the response from an asynchronous call?) The solution to this is not as simple as just moving contacts[i].social = results; into inside the request call success handler because the value of i will have changed before the handler is called.
Your second issue is that results is defined outside of the socialScrape function definition - so instead of having an array of items per request call, you have one array with all request results. The best way to resolve your scoping issues is with a closure, which we can achieve by removing the call to socialScrape(homepage); and making socialScrape a self-invoking function:
(function socialScrape(homepage) {
var results = [];
var index = i;
request(homepage, function(err, resp, html) {
/* do error and status check stuff and build our results array */
contacts[index].social = results;
});
}(homepage));
Notice how we capture the current value of i within the closure and assign it to index. This will allow us to get the correct contact by index when our result is delivered.

Related

PayPal Smart Buttons Error: "Expected order id to be passed"

i have a problem : I'm trying to figure out what it is since last friday, I can't find any good docs and no clues on the internet, I share some screenshots if anyone could help it would be awesome, by the way my problem is that when clicking on the paypal smart buttons that I integrated, i get in the console "expected an order id to be passed" unfortunately I can't find what is that order id thing
paypal.Buttons({
style: {
shape: 'rect',
color: 'gold',
layout: 'vertical',
label: 'pay',
},
// Sets up the transaction when a payment button is clicked
createOrder: function (data, actions) {
var cartArray = shoppingCart.listCart();
// Call your backend to create the Checkout Session
$.ajax({
data: {
id: cookie
},
type: "POST",
url: "create.php",
success: function (response) {
response = JSON.parse(response)
surname = []
surname = response.name.split(/(\s+)/).filter(e => e.trim().length > 0)
const Cart5 = [];
function Cart(description, name, unit_amount, quantity) {
this.description = description;
this.name = name;
this.unit_amount = {
value: unit_amount,
currency_code: 'EUR'
};
this.quantity = quantity;
this.category = 'PHYSICAL_GOODS';
}
cartArray.forEach(element => {
var item = new Cart(element.id, element.name, element.price, element.count);
Cart5.push(item);
})
JSON.stringify(Cart5);
orderid = makeid(12);
const paymentData = {
//authorization: 'AdhHUVsamn71V-Xs5JZVpzL4v6ElEKiYywV6PwF7rRwCwRQ-AZHMcLELmvQuWlS1pL19iiCbbZUIupTt',
intent: "CAPTURE",
env: "production",
application_context: {
brand_name: "Skunker",
locale: "fr-FR",
landing_page: "BILLING",
shipping_preference: "SET_PROVIDED_ADDRESS",
},
purchase_units: [{
reference_id: orderid,
custom_id: orderid,
description: 'Achat chez Skunker.Store',
invoice_id: orderid,
payer: {
email_address: response.email,
},
amount: {
currency_code: "EUR",
value: shoppingCart.totalCart() - (shoppingCart.totalCart() * sessionStorage.getItem('discount') / 100) + 6.15,
breakdown: {
item_total: {
currency_code: "EUR",
value: shoppingCart.totalCart()
},
tax_total: {
currency_code: "EUR",
value: 0.20
},
shipping: {
currency_code: "EUR",
value: 5.95
},
handling: {
currency_code: "EUR",
value: 0.00
},
insurance: {
currency_code: "EUR",
value: 0.0
},
shipping_discount: {
currency_code: "EUR",
value: 0.00
}
}
},
items: Cart5,
shipping: {
method: "La Poste - Colissimo",
address: {
name: {
given_name: surname[0],
surname: surname[1],
},
address_line_1: response.address,
admin_area_2: response.city,
postal_code: response.postalCode,
country_code: "FR"
},
phone_number: {
country_code: '33',
national_number: response.tel,
}
}
}]
}
return actions.order.create(paymentData);
}
})
},
// Finalize the transaction after payer approval
onApprove: function (data, actions) {
return actions.order.capture().then(function (orderData) {
// Full available details
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
// Show a success message within this page, e.g.
const element = document.getElementById('paypal-button-container');
element.innerHTML = '';
element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Successful capture! For dev/demo purposes:
$.ajax({
data: JSON.stringify(paymentData),
type: "POST",
url: "https://skunker.store/scripts/paid.php",
success: function (response) {}
})
// When ready to go live, remove the alert and show a success message within this page. For example:
// var element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
});
},
onError: function (err) {
console.log(err);
},
}).render('#payBtn');
And this is the error I get :
Error: Expected an order id to be passed
at https://www.sandbox.paypal.com/smart/buttons?style.label=pay&style.layout=vertical&style.color=gold&style.shape=rect&style.tagline=false&style.menuPlacement=below&components.0=buttons&locale.country=FR&locale.lang=fr&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jbGllbnQtaWQ9QVhTMWxLcXR2cjkzbDVkYlFPTE9nclJCYlNKMWt0cmNnYkpMZnJjRWEyckhING1ZYzVvWlQ1dmxpWUZDMnRobUFlcDVic1h4RmF1WEJ1cDMmY3VycmVuY3k9RVVSIiwiYXR0cnMiOnsiZGF0YS1zZGstaW50ZWdyYXRpb24tc291cmNlIjoiYnV0dG9uLWZhY3RvcnkiLCJkYXRhLXVpZCI6InVpZF9tdmh4dGh4aHhlYW13bHJzYXVna2dqeGRmcmpqenMifX0&clientID=AXS1lKqtvr93l5dbQOLOgrRBbSJ1ktrcgbJLfrcEa2rHH4mYc5oZT5vliYFC2thmAep5bsXxFauXBup3&sdkCorrelationID=f540177905bdc&storageID=uid_dbb0ba13c9_mtq6mzg6ndi&sessionID=uid_0e8f15ce50_mtq6ntq6nte&buttonSessionID=uid_1e5135ffcf_mtq6ntq6nte&env=sandbox&buttonSize=huge&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjpmYWxzZSwibWVyY2hhbnRDb25maWdIYXNoIjoiZmQxNWMxYTBkNTFiYjBlMTRjODkxYTUzNDYwZTZiYWU1MDkyZmEzZCIsInByb2R1Y3RzIjp7InBheUluMyI6eyJlbGlnaWJsZSI6ZmFsc2UsInZhcmlhbnQiOm51bGx9LCJwYXlJbjQiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXJpYW50IjpudWxsfSwicGF5bGF0ZXIiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXJpYW50IjpudWxsfX19LCJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJicmFuZGVkIjp0cnVlLCJpbnN0YWxsbWVudHMiOmZhbHNlLCJ2ZW5kb3JzIjp7InZpc2EiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sIm1hc3RlcmNhcmQiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sImFtZXgiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sImRpc2NvdmVyIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfSwiaGlwZXIiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXVsdGFibGUiOmZhbHNlfSwiZWxvIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfX0sImd1ZXN0RW5hYmxlZCI6ZmFsc2V9LCJ2ZW5tbyI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJpdGF1Ijp7ImVsaWdpYmxlIjpmYWxzZX0sImNyZWRpdCI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJhcHBsZXBheSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJzZXBhIjp7ImVsaWdpYmxlIjpmYWxzZX0sImlkZWFsIjp7ImVsaWdpYmxlIjpmYWxzZX0sImJhbmNvbnRhY3QiOnsiZWxpZ2libGUiOmZhbHNlfSwiZ2lyb3BheSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJlcHMiOnsiZWxpZ2libGUiOmZhbHNlfSwic29mb3J0Ijp7ImVsaWdpYmxlIjpmYWxzZX0sIm15YmFuayI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJwMjQiOnsiZWxpZ2libGUiOmZhbHNlfSwiemltcGxlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJ3ZWNoYXRwYXkiOnsiZWxpZ2libGUiOmZhbHNlfSwicGF5dSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJibGlrIjp7ImVsaWdpYmxlIjpmYWxzZX0sInRydXN0bHkiOnsiZWxpZ2libGUiOmZhbHNlfSwib3h4byI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJtYXhpbWEiOnsiZWxpZ2libGUiOmZhbHNlfSwiYm9sZXRvIjp7ImVsaWdpYmxlIjpmYWxzZX0sIm1lcmNhZG9wYWdvIjp7ImVsaWdpYmxlIjpmYWxzZX19&platform=desktop&experiment.enableVenmo=false&experiment.disablePaylater=false&experiment.enableVenmoAppLabel=false&flow=purchase&currency=EUR&intent=capture&commit=true&vault=false&renderedButtons.0=paypal&renderedButtons.1=card&debug=false&applePaySupport=false&supportsPopups=true&supportedNativeBrowser=false&allowBillingPayments=true:1338:178001
I tried putting only the amount into the data and it still doesn't work, I also tried to add an order ID but it doesn't work
This is a bit of a mess, you're doing an ajax call to a "create.php" on your server, but for some reason not calling the PayPal API from there (with the Checkout-PHP-SDK or similar), but are instead using that response to then create an order from the client-side (which is what actions.order.create does)
This doesn't make any real sense. If you are using a server, you should have two routes: create.php and capture.php?id=XXXXXXXXXXXXXXX" (use a JSON body parameter to fetch rather than GET URL string, if desired) both of which communicate with the PayPal API themselves, to create and capture an order respectively. It does not make sense to combine your own server operations with JS SDK client-side methods.
So, you should switch over completely to this server pattern example: https://developer.paypal.com/demo/checkout/#/pattern/server , and in each of the respective routes that are called (create.php / capture.php or similar) you can either write your own HTTPS calls for the PayPal API, or use the Checkout-PHP-SDK to abstract them as desired, and return the JSON response to the fetch caller.
I do not recommend trying to get what you have to work, but the main reason it is not working is a failure to return a JavaScript Promise properly. You are invoking an asynchronous $.ajax and not returning anything after doing so, hence the error "Expected an order id to be passed" since you actually returned nothing (even though you think you did); in reality you just spun off an asynchronous $.ajax to do its own thing and that function's result is never fed back (nor would it be if you returned its result, since it uses a callback rather than promises).
The simplest way to get a promise when calling your own server routes is to use fetch instead of $.ajax, then you can just return the result of the fetch. It is theoretically possible to turn a $.ajax callback into a promise and return that, but I would not spend any time on this if I were you; the built-in browser fetch creates a promise for you and is preferred in modern JS.

Filter duplicate values in select2 ajax call

I'm using select2 load remote data way to render results (50 at a time) from an api. The response of the api might have duplicate values in any page response.
I have tried formatting response but unfortunately the method is having access only to the current page data.
Below is my code,
jQuery('#items').select2({
minimumInputLength : 2,
placeholder : '-- Select Items --',
ajax : {
url : '/api/v1/items',
quietMillis : 200,
dataType : 'json',
data : function (term, page) {
return {
term : term,
page : page,
page_limit : 50
};
},
results : function(data, page) {
//Here I'm getting only current page data. How can i get previous page data to check for duplicate values.
}
}
});
So, how can I filter the response and eliminate duplicate values by checking against the data fetched so far.
Any help would be appreciated.
It would be better if you post an example of your code. Let's say you have some data with duplicated entries:
var rawData = [
{
id: 'AL',
name: 'Alaska'
},
{
id: 'GE',
name: 'Georgia'
},
{
id: 'WY',
name: 'Wyoming'
},
{
id: 'GE',
name: 'Georgia'
}
];
function clearDuplicates(data) {
var temp = {};
for (var i = 0; i < data.length; i++) {
temp[data[i]['id']] = data[i];
}
return Object.values(temp);
}
var clearData = clearDuplicates(rawData);
console.log(clearData);
See output: duplicated entry 'Georgia' is now in one record. There can be a lot of ways to eliminate duplicates. This is just one simple example.
UPDATE:
If you use pagination (infinite scroll) in Select2, every page request is sent separately and you have to process result data and eliminate duplicates manually. it can be done by processResults parameter. (See example)
In that case, easiest way would be:
Handle every page request in processResults
Store all results in a global variable
Eliminate duplicates as described in the example above
Return desired result
Return:
return {
results: <YOUR_FILTERED_DATA>,
pagination: {
//paginatioin params
}
}

it statement inside loop iteration for each expect statement, mocha

I have this array of objects.
let links = [
{
url: 'some url 1',
status: 200
},
{
url: 'some url 2',
status: 200
}
]
Which is the result of calling LinkFunction asyncronously inside before:
before(async () => {
try {
links = await LinkFunction();
} catch (err) {
assert.fail(err);
}
});
I would like to check if the url and status properties exist and if their types are correspondingly string and number.
Note: The specified object is just a sample of a big response. So the loop is required for iteration in any case.
I've done this iteration:
it('Array to contain some props', () => {
links.map(property => {
expect(property).to.have.property('url').to.be.a('string');
expect(property).to.have.property('status').to.be.a('number');
});
});
But I would like to have something like this:
it('Array to contain some props', () => {//or describe would be better here
links.map(property => {
it('link array to contain url string', () => {
expect(property).to.have.property('url').to.be.a('string');
});
it('link array to contain status number', () => {
expect(property).to.have.property('status').to.be.a('number');
});
});
});
Unfortunately it statements are ignored inside map. Maybe it's because of the several nested it statements. So How can I implement a similar logic?
Update:
My full code:
You might want to use forEach instead of map.
Also, "Passing arrow functions (aka "lambdas") to Mocha is discouraged" so you will probably want to change those to normal functions.
Having said that, it works fine if links is defined as mocha initially runs the test file and collects the individual it tests:
const expect = require('chai').expect;
describe('links', function() {
let links = [
{
url: 'some url 1',
status: 200
},
{
url: 'some url 2',
status: 200
}
]
links.forEach(function(property) {
it('link array to contain url string', function() {
expect(property).to.have.property('url').to.be.a('string');
});
it('link array to contain status number', function() {
expect(property).to.have.property('status').to.be.a('number');
});
});
});
..results in:
> mocha
links
√ link array to contain url string
√ link array to contain status number
√ link array to contain url string
√ link array to contain status number
4 passing (14ms)
Update
As you have found, it only works at the top level or with a describe:
before(function() {
it('will NOT work here', function() { });
});
it('will work here', function() {
it('will NOT work here', function() { });
});
Also, links must be available while the test is first running and the it tests are being collected by mocha so this doesn't work either:
describe('links', function() {
let links = [];
before(function() {
links = [
{
url: 'some url 1',
status: 200
},
{
url: 'some url 2',
status: 200
}
];
});
// This won't work...
links.forEach(function(property) {
// .. since links is still an empty array when this runs
it('should...', function() { /* ... */ });
});
});
From your question update it looks like your code retrieves links from an async function call in before. Because of this, there is no way to have links populated at the time the test is first running and the it tests are collected.
So it looks like you won't be able to map across the items in links to create your it tests, and will instead need to take the approach you described by mapping across the items in links within a single test.

Insert to db from Meteor server not working

I'm trying to run a simple script that will scrape some data using x-ray and insert it into my Events collection.
if (Meteor.isServer) {
var Xray = Meteor.npmRequire('x-ray');
var xray = new Xray({
version: "2.0.3"
});
xray('http://www.events12.com/seattle/january/', '.qq', [{
title: '.title',
date: '.date',
link: 'a #href',
allContent: '#html'
}])(function(err, content) {
for (var i = 0; i < content.length; i++) {
(function() {
console.log(i);
var newEvent = {
owner: 'me',
name: content[i].title,
date: content[i].date,
url: content[i].link,
createdAt: new Date(),
description: 'none'
};
console.log(newEvent);
Events.insert(newEvent, function(err, data) {
console.log(err);
console.log(data);
});
})();
}
});
}
The callback from x-ray that takes in content has all the scraped data in an array of objects, each with several properties. When I try to insert this data into my Events collection, the for loop iterates once and then exits, but no error is shown. If I remove the Events.insert() the loop iterates all the way through.
What am I missing? What is the proper way to execute such a task?
The Events.insert() was being called outside of any Meteor fibers. Adding Meteor.bindEnvironment() and feeding the entire function in as a callback fixed this problem.

Associate multiple resources to service appointment entity using OData endpoint with javascript in CRM 2013

My goal is to create a service appointment record associated with multiple resources.
For this purpose I have followed this MSDN example.
The problem rises when I try to associate multiple resources for one particular service appointment, CRM server will store only the last one(1 record). Following demonstrates my code:
//attendees =[... array of resource ids]
var serviceAppointment = {
ScheduledStart: new Date("12/22/2014 4:53 PM"),
ScheduledEnd: new Date("12/22/2014 5:53 PM"),
Subject: "test service",
ServiceId:
{
//// hardcoded id for simplicity
Id: "6f795012-ca55-e411-aa38-00155d0a0948",
LogicalName: "service"
}
};
SDK.JQuery.createRecord(serviceAppointment,"ServiceAppointment"
,function(sa){
for(var i=0;i<attendees.length;i++)
{
var activityParty = {
PartyId:
{
Id: attendees[i],
LogicalName: "systemuser",
},
ActivityId:
{
Id: sa.ActivityId,
LogicalName: "serviceappointment",
},
ParticipationTypeMask:
{
//See http://msdn.microsoft.com/en-us/library/gg328549.aspx
//10 is for resource
Value: 10
}
};
SDK.JQuery.createRecord(activityParty,"ActivityParty", function(ap){debugger;},errorHandler);
}
}
,errorHandler);
As far as I debugged the code the create record is being executed properly without no exception. I believe I'm missing a configuration flag somewhere in my code and CRM is considering one to one association rather than one to many.
Any clues?
I could solve this issue by passing array of parties in service appointment record at the first place rather than inserting them one-by-one at the end. Here is the code which works:
var parties =[];
for(var i=0;i< e.event.attendees.length;i++)
{
var activityParty = {
PartyId:
{
Id: e.event.attendees[i],
LogicalName: "systemuser",
},
ParticipationTypeMask:
{
//See http://msdn.microsoft.com/en-us/library/gg328549.aspx
//10 is for resource
Value: 10
}
}
parties.push(activityParty);
}
var serviceAppointment = {
ScheduledStart: e.event.start,
ScheduledEnd: e.event.end,
Subject: e.event.title,
ServiceId:
{
Id: "6f795012-ca55-e411-aa38-00155d0a0948",
LogicalName: "service"
},
serviceappointment_activity_parties: parties,
};
SDK.JQuery.createRecord(serviceAppointment,"ServiceAppointment"
,function(sa){
debugger;
e.event.ActivityId = serviceAppointmentActivityId = sa.ActivityId;
}
,errorHandler);
}
hope it helps somebody out there.

Categories

Resources