I'm trying to return an object outside of a function. I'm using an Angular JS promise to log the availableProviders when they've been loaded, which is logging correctly to my console.
function getServiceProviders(serviceId) {
var serviceProviders = ref.child('services').child(serviceId).child('providers');
var providers = ref.child('providers');
serviceProviders.on('value', function(snapshot) { // on services.serviceId.providers
var availableProviders = {}; // create empty availableProviders array
snapshot.forEach(function(childSnapshot) { // for each provider in services.serviceId.providers
var key = childSnapshot.key(); // grab each provider's key
providers.on('value', function(snap) { // on providers
if (snap.hasChild(key)) { // if providers has a child that matches the var key above
var item = snap.child(key); // store that child in a var called item
availableProviders[item.key()] = item.val(); // add item to availableProviders array
}
});
}); // rinse and repeat
var defer = $q.defer();
defer.promise
.then(function() {
console.log(availableProviders);
})
defer.resolve();
});
return availableProviders;
}
I want the getServiceProviders() function to return these availableProviders, but I'm getting this error asavailableProviders` isn't defined outside of that function.
ReferenceError: availableProviders is not defined
Is there any way around this. Any help is appreciated. Thanks in advance!
Problem related to Closures in JavaScript, I think you need to move var availableProviders = {}; outside serviceProviders.on('value', function(snapshot) { function will fix your issue.
Code
function getServiceProviders(serviceId) {
var serviceProviders = ref.child('services').child(serviceId).child('providers');
var providers = ref.child('providers');
var availableProviders = {}; // <==made it global
serviceProviders.on('value', function(snapshot) { // on services.serviceId.providers
snapshot.forEach(function(childSnapshot) { // for each provider in services.serviceId.providers
var key = childSnapshot.key(); // grab each provider's key
providers.on('value', function(snap) { // on providers
if (snap.hasChild(key)) { // if providers has a child that matches the var key above
var item = snap.child(key); // store that child in a var called item
availableProviders[item.key()] = item.val(); // add item to availableProviders array
}
});
}); // rinse and repeat
var defer = $q.defer();
defer.promise
.then(function() {
console.log(availableProviders);
})
defer.resolve();
});
return availableProviders;
}
Related
I've been trying to code up a search engine using angular js, but I can't copy one array to another. When I initiate the the code (in the service.FoundItems in the q.all then function) new array(foundArray) shows up as an empty array. I searched up how to copy one array to another and tried that method as you can see, but it isn't working. Please help, here is the code, and thank you.
P.S. if you need the html please tell me.
(function () {
'use strict';
angular.module('narrowDownMenuApp', [])
.controller('narrowItDownController', narrowItDownController)
.service('MenuSearchService', MenuSearchService)
.directive('searchResult', searchResultDirective);
function searchResultDirective() {
var ddo = {
templateUrl: 'searchResult.html',
scope: {
items: '<'
},
};
return ddo
}
narrowItDownController.$inject = ['MenuSearchService'];
function narrowItDownController(MenuSearchService) {
var menu = this;
menu.input = "";
menu.displayResult = [];
menu.searchX = function(name) {
menu.displayResult = MenuSearchService.FoundItems(menu.input, name);
console.log(menu.displayResult);
};
}
MenuSearchService.$inject = ['$http', '$q'];
function MenuSearchService($http, $q) {
var service = this;
service.getMatchedMenuItems = function(name, searchTerm) {
var deferred = $q.defer();
var foundItems = [];
var result = $http({
method: "GET",
url: ('https://davids-restaurant.herokuapp.com/menu_items.json'),
params: {
category: name
}
}).then(function (result) {
var items = result.data;
for (var i = 0; i < items.menu_items.length; i++) {
if (searchTerm === ""){
deferred.reject("Please enter search term");
i = items.menu_items.length;
}
else if (items.menu_items[i].name.toLowerCase().indexOf(searchTerm.toLowerCase()) ==! -1){
foundItems.push(items.menu_items[i].name)
deferred.resolve(foundItems);
}else {
console.log("doesn't match search");
}
}
});
return deferred.promise;
};
service.FoundItems = function (searchTerm, name) {
var searchResult = service.getMatchedMenuItems(name, searchTerm);
var foundArray = [];
$q.all([searchResult])
.then(function (foundItems) {
foundArray = foundItems[0].slice(0);
foundArray.reverse();
})
.catch(function (errorResponse) {
foundArray.push(errorResponse);
});
console.log(foundArray);
return foundArray;
};
};
})();
If the goal of the service.FoundItems function is to return a reference to an array that is later populated with results from the server, use angular.copy to copy the new array from the server to the existing array:
service.FoundItems = function (searchTerm, name) {
var foundArray = [];
var searchPromise = service.getMatchedMenuItems(name, searchTerm);
foundArray.$promise = searchPromise
.then(function (foundItems) {
angular.copy(foundItems, foundArray);
foundArray.reverse();
return foundArray;
})
.catch(function (errorResponse) {
return $q.reject(errorResponse);
})
.finally(function() {
console.log(foundArray);
});
return foundArray;
};
I recommend that the promise be attached to the array reference as a property named $promise so that it can be used to chain functions that depend on results from the server.
Frankly I don't recommend designing services that return array references that are later populated with results. If you insist on designing it that way, this is how it is done.
I tried the $promise thing that you recommended. I was wondering how you would get the value from it ie the array.
In the controller, use the .then method of the $promise to see the final value of the array:
narrowItDownController.$inject = ['MenuSearchService'];
function narrowItDownController(MenuSearchService) {
var menu = this;
menu.input = "";
menu.displayResult = [];
menu.searchX = function(name) {
menu.displayResult = MenuSearchService.FoundItems(menu.input, name);
̶c̶o̶n̶s̶o̶l̶e̶.̶l̶o̶g̶(̶m̶e̶n̶u̶.̶d̶i̶s̶p̶l̶a̶y̶R̶e̶s̶u̶l̶t̶)̶;̶
menu.displayResult.$promise
.then(function(foundArray) {
console.log(foundArray);
console.log(menu.displayResult);
}).catch(function(errorResponse) {
console.log("ERROR");
console.log(errorResponse);
});
};
}
To see the final result, the console.log needs to be moved inside the .then block of the promise.
Titus is right. The function always immediately returns the initial value of foundArray which is an empty array. The promise is executed asynchronously so by the time you are trying to change foundArray it is too late. You need to return the promise itself and then using .then() to retrieve the value just like you are currently doing inside the method.
From just quickly looking at your code I think you made have a simple error in there. Are you sure you want
foundArray = foundItems[0].slice(0);
instead of
foundArray = foundItems.slice(0);
myData is generated in myFactory, and the following code works if myData is generated from a single myHttp.GetGroupMember function.
However, it is not working with multiple GetGroupMember calls as they execute "at the same time", and dataContainer gets initialized from createMyData call.
How can I call second GetGroupMember after first GetGroupMember is "finished"?
var myFactory = function (myHttp) {
var myData = [];
var dataContainer = [];
var numProgress;
var onSuccess = function(data_member) {
myData.push(data_member);
numProgress++;
loadMember(dataContainer); // if member is succefully loaded, load next member
// if numProgress == data_member.length, call next GetGroupMember() here?
}
var loadMember = function(data_group) {
if (numProgress < data_group.length) {
myHttp.getMemberDetails(data_group.division, data_group.members[numProgress]).then(onSuccess, onError);
}
}
var createMyData = function(response) {
dataContainer = response; // coded as shown to call loadMember() iteratively
loadMember(dataContainer);
}
var getMyData = function(init) {
myHttp.getGroupMember("Division1", "Group_Foo").success(createMyData); // getGroupMember_1
// how to call getGroupMember_2 after getGroupMember "finished"?
myHttp.getGroupMember("Division2", "Group_Bar").success(createMyData); // getGroupMember_2
return myData;
}
}
var myControl = function($scope, myFactory) {
$scope.myData = myFactory.getMyData();
}
Easiest thing in the world:
myHttp.getGroupMember("Division1", "Group_Foo")
.success(createMyData)
.then(function() {
return myHttp.getGroupMember("Division2", "Group_Bar")
}).success(createMyData);
Read up on promises.
I'm currently working on this function, and the return value returns with undefined. I'm not sure if it is my lack of understanding how javascript operates, or it requires some tricky voodoo to make my code work.
var fs = require('fs');
var _ = require('underscore');
function eventValue(current, choice) {
var output = [];
if (current != null) {
var json;
fs.readFile('./json.json', 'utf8', function(err, data) {
if (err) {
throw err
}
json = JSON.parse(data);
// filter by ID
var filtered = _.filter(json, {
'ID': current
});
//console.log(filtered);
// Get ID of Object
var ID = _.pluck(filtered, 'ID');
// Get Next value of object
var NEXT = _.pluck(filtered, 'next');
// get nested 'choice's 'next' value
var collect = _.pluck(_.filter(
_.flatten(
_.pluck(filtered, 'choice')), {
'choice': choice
}), 'next');
var stringID = String(ID);
var stringNext = String(NEXT + collect);
output = [stringID, stringNext];
return output;
})
} else console.log("[[error]] please populate eventValue()");
};
var a = eventValue("001001A01B01");
console.log(a);
You are trying to use value returned from asynchronous function callback which you can not.
Refer: How to get returned value by function with callback inside
I have been working around this for a really long while and I think I'm sort of giving up and coming here to ask now. I have a cloud code function that first lets me get a list of categories. Every category has multiple products. This one-to-many relationship is carried out using pointers, where every product points to the category it belongs to. However I'm failing to retrieve any products.
This my code,
Parse.Cloud.define("GetCategories", function(request, response) {
var _ = require('underscore.js')
var MSTCATEGORY_CLASS = "MSTCategory";
var MSTPRODUCT_CLASS = "MSTProduct";
var CATEGORY_TAG = "Category";
var SHOP_TAG = "Shop";
var VIEWS_TAG = "Views";
var CAT_LIMIT = 10;
var PROD_LIMIT = 5;
var productList = [];
var CatAndProd = [];
var MSTCategory = Parse.Object.extend(MSTCATEGORY_CLASS);
var query = new Parse.Query(MSTCategory);
query.limit(CAT_LIMIT);
query.find().then(function(results) {
var promise = Parse.Promise.as();
_.each(results, function(result) {
promise = promise.then(function() {
var MSTProduct = Parse.Object.extend(MSTPRODUCT_CLASS);
var ProdQuery = new Parse.Query(MSTProduct);
ProdQuery.equalTo(CATEGORY_TAG, result);
ProdQuery.include(CATEGORY_TAG);
ProdQuery.include(SHOP_TAG);
ProdQuery.descending(VIEWS_TAG);
ProdQuery.limit(PROD_LIMIT);
var category = result;
ProdQuery.find().then(function(ProdResults){
ProdResults.forEach(function(product) {
productList.push(product);
});
var singleItem = {
"category" : category,
"products" : productList
};
CatAndProd.push(singleItem);
return Parse.Promise.as("Hello!");
});
});
});
return promise;
}).then(function(hello) {
var jsonObject = {
"categoryAndProducts": CatAndProd
};
response.success(jsonObject);
});
});
What I am trying to do is after getting the category, I'd fetch products in it, add them to a jsonObject. And once I'm done with all categories. I'll create an array to carry all those json objects and send it as response. This is really basic, I'm pretty sure it's become my logic is incorrect. I'm new to Javascript and Parse.
There are few errors:
in your _.each, you are overwriting the same promise object in each loop
you can return directly your singleItem via the promise resolving, not need for an extra array
you're using a global productList array so the last category will have ALL products.
Try like this, I put few comments in the code:
query.find().then(function(results) {
// an array to store all our promises
var promises = [];
results.forEach(function(result) {
// one promise for each result
var promise = new Parse.Promise();
var MSTProduct = Parse.Object.extend(MSTPRODUCT_CLASS);
var ProdQuery = new Parse.Query(MSTProduct);
ProdQuery.equalTo(CATEGORY_TAG, result);
ProdQuery.include(CATEGORY_TAG);
ProdQuery.include(SHOP_TAG);
ProdQuery.descending(VIEWS_TAG);
ProdQuery.limit(PROD_LIMIT);
var category = result;
ProdQuery.find().then(function(ProdResults) {
// remove this
// ProdResults.forEach(function(product) {
// productList.push(product);
// });
var singleItem = {
"category" : category,
"products" : ProdResults
};
// pass singleItem directly when resolving the promises
promise.resolve(singleItem);
});
promises.push(promise);
});
// return a promise resolving when ALL Promises are resolved
return Parse.Promise.when(promises);
}).then(function(allProducts) {
// allProducts is an array of singleItem
var jsonObject = {
"categoryAndProducts": allProducts
};
response.success(jsonObject);
});
Also, try to not mix Array.forEach and _.each, use one of them but not both at the same time.
I'm newbie to js and this is my first question in stackoverflow as well. So any comment or act of downgrading is understandable.
This is the angular-js-flowchart project on github.
This is another stackoverflow topic that teachs how to use factory as a data getter involving $http.
My need is to generate data for the chart by using an Angular factory that returns a $http function. The $http talks to a php service that retrieve data from database. I have tested the service using jsonlint and its working fine. The directory of service is checked, relatively to the html file.
I copied the "factory" code from another stackoverflow question and applied to app.js in the angularjs-flowchart Github project.
The problem is that the Chrome console keeps throwing an error that I can not understand. Data is not retrieved. The error on console is "TypeError: Cannot read property 'getData' of undefined"
This is the modified-by-me app.js:
//
// Define the 'app' module.
//
angular.module('app', ['flowChart', ])
//
// Simple service to create a prompt.
//
.factory('prompt', function () {
/* Uncomment the following to test that the prompt service is working as expected.
return function () {
return "Test!";
}
*/
// Return the browsers prompt function.
return prompt;
})
//
// Application controller.
//
.controller('AppCtrl', ['$scope', 'prompt', function AppCtrl ($scope, prompt, dataFactory) {
//
// Code for the delete key.
//
var deleteKeyCode = 46;
//
// Code for control key.
//
var ctrlKeyCode = 65;
//
// Set to true when the ctrl key is down.
//
var ctrlDown = false;
//
// Code for A key.
//
var aKeyCode = 17;
//
// Code for esc key.
//
var escKeyCode = 27;
//
// Selects the next node id.
//
var nextNodeID = 10;
//
// Event handler for key-down on the flowchart.
//
$scope.keyDown = function (evt) {
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = true;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Event handler for key-up on the flowchart.
//
$scope.keyUp = function (evt) {
if (evt.keyCode === deleteKeyCode) {
//
// Delete key.
//
$scope.chartViewModel.deleteSelected();
}
if (evt.keyCode == aKeyCode && ctrlDown) {
//
// Ctrl + A
//
$scope.chartViewModel.selectAll();
}
if (evt.keyCode == escKeyCode) {
// Escape.
$scope.chartViewModel.deselectAll();
}
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = false;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Add a new node to the chart.
//
$scope.addNewNode = function () {
var nodeName = prompt("Enter a task name:", "New Task");
if (!nodeName) {
return;
}
//
// Template for a new node.
//
var newNodeDataModel = {
name: nodeName,
id: nextNodeID++,
x: 0,
y: 0,
inputConnectors: [
{
name: "Pre"
}
],
outputConnectors: [
{
name: "Sub"
}
],
};
$scope.chartViewModel.addNode(newNodeDataModel);
};
//
// Add an input connector to selected nodes.
//
$scope.addNewInputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addInputConnector({
name: connectorName,
});
}
};
//
// Add an output connector to selected nodes.
//
$scope.addNewOutputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addOutputConnector({
name: connectorName,
});
}
};
//
// Delete selected nodes and connections.
//
$scope.deleteSelected = function () {
$scope.chartViewModel.deleteSelected();
};
//
// Setup the data-model for the chart.
//
var chartDataModel = {};
var handleSuccess = function(data, status){
chartDataModel = data;
console.log(chartDataModel);
};
dataFactory.getData().success(handleSuccess);
//
// Create the view-model for the chart and attach to the scope.
//
$scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
}])
.factory('dataFactory', function($http){
return {
getData : function(){
return $http.post("chart-data-retrieve.php");
}
};
});
Basically, what i added but doesn't work is
// Setup the data-model for the chart.
//
var chartDataModel = {};
var handleSuccess = function(data, status){
chartDataModel = data;
console.log(chartDataModel);
};
dataFactory.getData().success(handleSuccess);
and
.factory('dataFactory', function($http){
return {
getData : function(){
return $http.post("chart-data-retrieve.php");
}
};
});
Please help, thanks.
I tried to set the chartViewModel of the $scope directly inside the service call, so the variable chartDataModel becomes redundant. And it works.
// Create the view-model for the chart and attach to the scope.
//
myService.then(function(data) {
$scope.chartViewModel = new flowchart.ChartViewModel(data);
});
I tried to return a promise, not a $http from the factory. It works now. The controller can now use the service to retrieve data. However I still could not set the controller's variable to the data retrieved.
The following is the code:
.factory('myService', function($http, $q) {
//this runs the first time the service is injected
//this creates the service
var deferred = $q.defer();
$http.get('chart-data-retrieve.php').then(function(resp) {
deferred.resolve(resp.data);
});
return deferred.promise;
})
And the code inside controller:
var chartDataModel = {};
//get data from myService factory
myService.then(function(data) {
alert(data);
chartDataModel = data;
});
Currently, the alert() show me the data already. However, the variable chartDataModel is still unset.