I have a filtration function, a lot of checkboxes and dropdowns. When user selects multiple check boxes and selects the dropdown values they click on the "Filter Now" button.
That button then carries out a POST request to my API and pass along the filtration options as the parameters and return the data from MongoDB.
Heres my code:
factory.getFilteredProjects = function(regions, services, sector){
return $http.post('/user/test',{
region: regions,
sol: services,
sec: sector
}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
console.log("this is the response data " + data);
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
};
return factory;
});
In the above code you can see I have 3 parameters (regions, services, sector)
Now the user only might want to filter the data by:
Regions or Sector
Just Regions
Just Services
Services and Regions
And So On!
My question:
How can I pass options parameters with my POST regions. At the moment I have to send all 3 parameters to get data back. If I don't send all 3 then I don't get any data back. Only the ones the user actually interacted with so basically something like:
// This is just to get my point across.
function(a || b || c){
}
UPDATE:
Testing my API through POSTMan. As you can see I only sent 2 parameters and got a 200 status back and i am also getting the correct data back.
Thanks.
You can just give an object in parameter instead of three string. Like this you can control the number of POST parameters instead of have some of them undefined.
EDIT : I suggest to do the filtering in your service. Like this, you don't have to complexify your code on each controller :
factory.getFilteredProjects = function(params){
// remove empty value or empty array
angular.forEach(params, function(value, key) {
if( ! value || value.length === 0 ) {
delete params[key];
}
})
return $http.post('/user/test', params).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
console.log("this is the response data " + data);
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
};
return factory;
});
getFilteredProjects({ region: 'test', sec: 'sector' })
so after doing some research, a few cups of coffee and couple of swear words later I got this working.
But heres a note:
First thanks to Yoann Prot your suggestion was a huge help!
This is my initial solution, I will NOT be accepting my own answer. Because there might be a better solution to this and I would like someone to post an answer/comment if they think it can be improved
So my if you read the comments above you know my API was able to handle multiple or flexible number parameters. The issue was my HTTP Post function required all parameters to be present.
As Alex Blex suggested in the comments that:
Then you just need to detect which ones the user actually interacted, and send only them
And thats exactly what I did.
I created a filter object to which I added key/value pairs of only the options that the user interacted with and passed that whole filter object as the parameter. This made my parameters for the HTTP Post request much more flexible.
Heres the code:
var filterObj = {};
var form = document.getElementById("regionPicker");
var servicesForm = document.getElementById("servicesPicker");
var inputs = form.getElementsByTagName("input");
var arr = [];
var servicesInput = servicesForm.getElementsByTagName("input");
var servicesArr = [];
for (var i = 0; i < inputs.length; i += 1) {
// Take only those inputs which are checkbox
if (inputs[i].type === "checkbox" && inputs[i].checked) {
arr.push(inputs[i].value);
}
}
for (var i = 0; i < servicesInput.length; i += 1) {
// Take only those inputs which are checkbox
if (servicesInput[i].type === "checkbox" && servicesInput[i].checked) {
servicesArr.push(servicesInput[i].value);
}
}
// here arr contains an array of filter options selected by user
filterObj.region = (arr.length > 0) ? arr:"";
// here serviceArr contains an array of another filter options selected by user
filterObj.sol = (servicesArr.length > 0) ? servicesArr:"";
And finally pass the filterObj as the parameter:
factory.getFilteredProjects = function(filterObj){
return $http.post('/user/test',filterObj)
.success(function(data, status, headers, config) {
console.log("this is the response data " + data.length);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
};
So far all the tests have been successful. But I repeat if you know a better solution please let me know or if you think this solution has drawbacks or issues or is just bad practice please let me know.
Thanks!
Related
called by selectbox go into function 'getDepAndMan()',
there is a value taken from the selectbox (works)
calls functions in the controller 'GetDepartmentAndManager' (works)
controller returns value (works)
{Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<<>f__AnonymousType6<'string, string>>}
Result View: [0] { UserDepartament = "there is value here / string", UserManager = "there is value here / string" }
should go back to ajax and call 'succes: function (employee data)' (works)
should assign values to the fields (doesn't work)
show an alert (work)
show alert with values (doesn't work, show an alert with: undefined undefined)
View:
#(Html
.DevExtreme()
.SelectBox()
.DataSource(d => d
.Mvc()
)
.OnValueChanged("getDepAndMan")
)
#(Html.DevExtreme().TextBox()
.ID("Id_department")
.ReadOnly(true)
)
#(Html.DevExtreme().TextBox()
.ID("Id_manager")
.ReadOnly(true)
)
<script type="text/javascript">
function getDepAndMan() {
var userId = {
nazwaValueId: $("#idName").dxSelectBox("instance").option("value")
};
$.ajax({
url: "#Url.Action("GetDepartmentAndManager", "Uzytkownicy")",
type: "POST",
dataType: "json",
data: {"userId": JSON.stringify(userId)},
cache: false,
success: function (danePracownika) {
$("#Id_department")
.dxTextBox("instance")
.option("value", danePracownika.UserDepartament);
$("#Id_manager")
.dxTextBox("instance")
.option("value", danePracownika.UserManager);
alert(danePracownika.UserDepartament + " " + danePracownika.UserManager);
},
failure: function (error) {
alert(error);
},
error: function (error) {
alert(error);
}
});
}
</script>
Controller:
[HttpPost]
public ActionResult GetDepartmentAndManager(string userId)
{
dynamic serializer = JsonConvert.DeserializeObject<IDictionary>(userId);
var IdCzlowieka = serializer["nazwaValueId"];
int IntIdCzlowieka = Convert.ToInt32(IdCzlowieka);
var danePracownika = _uzytkownicyContext.Uzytkownicy.Where(x => x.Id == IntIdCzlowieka).Select(s => new
{
UserDepartament = s.Departament,
UserManager = s.ManagerLogin
});
return Json(danePracownika);
}
return : //
[0] { UserDepartament = "there is value here / string", UserManager = "there is value here / string" }
EDIT
The question is, what's wrong with the code, why it doesn't work for me?
.
I see that in Your GetDepartmentAndManager You are not using Your passed parameter userID:
var danePracownika = ... .Where(x => x.Id == IntIdCzlowieka)...
should be Where(x => x.Id == userId) instead.
The next thing that came to me is the value You are acctualy getting inside the controller action; based on the JS code I would say that this is not the ID of the employee what You are passing but the stringified object { "nazwaValueId": ... } that in the best case would be handled by the server and You will get the raw string as a value of userId (unless You have defined a IModelBinder class that would handle conversion from stringified { "nazwaValueId": ... } to the value of that field - more on that You can find here).
Oh any by the way - please try to avoid mixing languages. I have a friend in the company which was forced to work with the german project and all their code was written in German - You would DEFINETLY won't be happy working with it. But if this a project made only by PL for PL, that is some kind of acceptable approach I assume.
Also I highly advice You to not use HTTP POST method for getting data. To make long story short there is a convention that GET requests are for getting the data and You can call it as many times You like without affecting the state (było takie mądre słowo na to, ale nie pamiętam ;)) and POST is for saving/modifing data and should always redirect to GET method on return. You can read more about it here.
EDIT:
Ok, for some reason I have found that the call in the current form is sending data not as a body but as a form. I don't know, I don't use jQuery. But here is the reqest:
so I changed the signature of the action to
public ActionResult GetDepartmentAndManager([FromForm]string userId)
to get is started working. Maybe on Your side it is just working fine, I don't know. But what I have found is that while sending the responce to the client we end up with... this:
so as You can see either Ajax or server changed the JSON keys to be kebabCase not PascalCase and that's why You are getting undefined values. Because properties You arereading do not exists. Just check it out: alert(danePracownika.userDepartament + " " + danePracownika.userManager);
UPDATE:
I checked it, it was not server's fault:
I am trying out some angularjs stuff. So I have a few arrays. One of them is artists. this is it's basic structure from console.log(artists);
artists
problem is that I can't access the elements of the array individually. I read up a lot of things regarding associative arrays and may questions on SO but none really helped. So either it is a very silly mistake I am making or it is some thing else.
Here are few results that I got with every array I have.
console.log(artists[0]); //returns undefined
console.log(artists['0']); //returns undefined
console.log(artists.length); // returns 0 in spite of the fact it showed 20 previously
console.log(Array.isArray(artists)); //returns true
And yes I created the array like this in a service, ChartService
var artists = [];
var artistids = [];
var tracks = [];
$http.get('https://api.spotify.com/v1/search/?q=genre:pop&type=artist').success(function (data) {
var items = data.artists.items;
items.forEach(function(item){
artists.push(item.name);
artistids.push(item.id);
var query = trackApi+item.id+'/top-tracks?country=SE'
$http.get(query).success(function (response) {
tracks.push({'preview': response.tracks[0].preview_url});
});
});
});
return {
Artists : artists,
Tracks : tracks
}
And my controller
console.log(ChartService.Artists); //runs fine
console.log(ChartService.Tracks); //runs fine
$scope.tracks = ChartService.Tracks;
console.log($scope.tracks); //runs fine
console.log($scope.tracks[0]); //returns undefined
console.log($scope.tracks['0']); //returns undefined
console.log($scope.tracks.length); // returns 0 in spite of the fact it showed 20 previously
console.log(Array.isArray($scope.tracks)); //returns true
The issue is that you check the content of artists before the issued http get requests have triggered their responses.
One way to resolve that is to put your code in the success callback, like this:
$http.get('https://api.spotify.com/v1/search/?q=genre:pop&type=artist').success(function (data) {
var items = data.artists.items;
items.forEach(function(item){
artists.push(item.name);
artistids.push(item.id);
var query = trackApi+item.id+'/top-tracks?country=SE'
$http.get(query).success(function (response) {
tracks.push({'preview': response.tracks[0].preview_url});
});
});
// here
console.log(artists);
});
Still, that solves it for artists, but then you'd need to do something similar if you need the tracks: as you have more then one request providing for that, you'd need to check the length of the tracks array and only if it has the complete length, like this:
$http.get('https://api.spotify.com/v1/search/?q=genre:pop&type=artist').success(function (data) {
var items = data.artists.items;
items.forEach(function(item){
artists.push(item.name);
artistids.push(item.id);
var query = trackApi+item.id+'/top-tracks?country=SE'
$http.get(query).success(function (response) {
tracks.push({'preview': response.tracks[0].preview_url});
if (tracks.length == items.length) { // all done
console.log(artists, tracks);
}
});
});
});
In a follow-up question (in comments) you explained you need this in your controller. You might look into $watch or variants of that method. If you need assistance with that, I would suggest to ask a new question.
I've created a couple of services that grab data from a REST API. These services return objects with names, ids, and some unique keys pertaining to a foo or a bar name. I also have a service doing the same for businesses, also with names, ids, and what foo/bar is tied to that business.
Unfortunately, the data model for this is...not ideal. Rather than just showing which foo/bar is attached to that business, it has every single foo or bar for every single business with a published: true/false key/val pair.
What I'm attempting to do is grab the URL name, loop through my foo object, check to see if the name from the current URL and the data match, and if they do store that object in $scope.results. From here, I want to loop through my businesses object and check to see if its conditionData id matches that of the new $scope.results array's id. Once this condition is met, I want to store those businesses in a $scope.businesses array. As it stands right now, I'm getting all businesses returned, rather than just the ones that have the same id as the current $scope.results id. I suspect the issue is either a) I'm a noob (most likely) or b) the published: true/false is creating issues.
Thanks in advance for any help, let me know if I need to clarify anything else. I'm still pretty new to Angular and JS as a whole, so I'm not sure if how I'm attempting to do this is super optimal. I'm open to better ideas if anyone has any.
.controller('ResultsController', function($scope, $location, getData) {
$scope.businesses = [];
$scope.results = [];
var url = $location.path().split('/')[2]; // we do this because it's always going to follow a pattern of /:base/:name
function init() {
getData.getConditions().success(function(data) {
var tempCondition = data;
var tempData;
for (var condition in tempCondition) {
tempData = tempCondition[condition];
if (url === tempData.name) {
$scope.results = tempData;
}
}
})
.error(function(data, status, headers, config) {
console.log('err: ' + data);
});
getData.getBusinesses().success(function(data) {
var tempBusinesses = data,
tempConditionData;
for (var business in tempBusinesses) {
tempConditionData = tempBusinesses[business].conditionData;
for (var condition in tempConditionData) {
if (tempConditionData[condition].id === $scope.results.id) {
$scope.businesses.push(tempBusinesses[business]);
}
}
}
})
.error(function(data, status, headers, config) {
console.log('err: ' + data);
});
}
init();
});
I find myself using SO as a rubber duck most of the time, I figured it out basically as soon as I finished typing the question. It was due to the published: true/false key/val pair.
All I had to do was change
for (var condition in tempConditionData) {
if (tempConditionData[condition].id === $scope.results.id) {
$scope.businesses.push(tempBusinesses[business]);
}
}
to
for (var condition in tempConditionData) {
if (tempConditionData[condition].id === $scope.results.id && tempConditionData[condition].published === true ) {
$scope.businesses.push(tempBusinesses[business]);
}
}
The two http calls you are using may also be problematic as they depend one each other. what if the first calls takes some time, your second http call returns first.
I'm trying to convert my basic crud operations into an API that multiple components of my application can use.
I have successfully converted all methods, except the update one because it calls for each property on the object to be declared before the put request can be executed.
controller
$scope.update = function(testimonial, id) {
var data = {
name: testimonial.name,
message: testimonial.message
};
dataService.update(uri, data, $scope.id).then(function(response) {
console.log('Successfully updated!');
},
function(error) {
console.log('Error updating.');
});
}
dataService
dataService.update = function(uri, data, id) {
var rest = Restangular.one(uri, id);
angular.forEach(data, function(value, key) {
// needs to be in the format below
// rest.key = data.key
});
// needs to output something like this, depending on what the data is passed
// rest.name = data.name;
// rest.message = data.message;
return rest.put();
}
I tried to describe the problem in the codes comments, but to reiterate I cannot figure out how to generate something like rest.name = data.name; without specifying the name property because the update function shouldn't need to know the object properties.
Here is what the update method looked like before I started trying to make it usable by any of my components (this works)
Testimonial.update = function(testimonial, id) {
var rest = Restangular.one('testimonials', id);
rest.name = testimonial.name;
rest.message = testimonial.message;
return rest.put();
}
How can I recreate this without any specific properties parameters hard-coded in?
Also, my project has included lo-dash, if that helps, I don't know where to start with this problem. Thanks a ton for any advice!
Try like
angular.extend(rest,testimonial)
https://docs.angularjs.org/api/ng/function/angular.extend
In my Win 8 app, based on a blank template, I have successfully added search contract and it seems to work despite the fact that I have not linked it to any data yet, so, for now, when I search any term in my app it simply takes me to the searchResults page with the message "No Results Found" this is what I was expecting initially.
Now what I wish to do is link my database into the searchResults.js file so that I can query my database. Now outside of the search contract I have tested and connected my Db and it works; I did this using WinJS.xhr, to connect to my web-service which in turn queries my database and returns a JSON object.
In my test I only hardcoded the url, however I now need to do two things. Move the test WinJS.xr data for connecting my DB into the search contract code, and second - change the hardcoded url to a dynamic url that accepts the users search term.
From what I understand of Win 8 search so far the actual data querying part of the search contract is as follows:
// This function populates a WinJS.Binding.List with search results for the provided query.
_searchData: function (queryText) {
var originalResults;
// TODO: Perform the appropriate search on your data.
if (window.Data) {
originalResults = Data.items.createFiltered(function (item) {
return (item.termName.indexOf(queryText) >= 0 || item.termID.indexOf(queryText) >= 0 || item.definition.indexOf(queryText) >= 0);
});
} else {`enter code here`
originalResults = new WinJS.Binding.List();
}
return originalResults;
}
});
The code that I need to transfer into this section is as below; now I have to admit I do not currently understand the code block above and have not found a good resource for breaking it down line by line. If someone can help though it will be truly awesome! My code below, I basically want to integrate it and then make searchString be equal to the users search term.
var testTerm = document.getElementById("definition");
var testDef = document.getElementById("description");
var searchString = 2;
var searchFormat = 'JSON';
var searchurl = 'http://www.xxx.com/web-service.php?termID=' + searchString +'&format='+searchFormat;
WinJS.xhr({url: searchurl})
.done(function fulfilled(result)
{
//Show Terms
var searchTerm = JSON.parse(result.responseText);
// var terms is the key of the object (terms) on each iteration of the loop the var terms is assigned the name of the object key
// and the if stament is evaluated
for (terms in searchTerm) {
//terms will find key "terms"
var termName = searchTerm.terms[0].term.termName;
var termdefinition = searchTerm.terms[0].term.definition;
//WinJS.Binding.processAll(termDef, termdefinition);
testTerm.innerText = termName;
testDef.innerText = termdefinition;
}
},
function error(result) {
testDef.innerHTML = "Got Error: " + result.statusText;
},
function progress(result) {
testDef.innerText = "Ready state is " + result.readyState;
});
I will try to provide some explanation for the snippet that you didn't quite understand. I believe the code you had above is coming from the default code added by Visual Studio. Please see explanation as comments in line.
/**
* This function populates a WinJS.Binding.List with search results
* for the provided query by applying the a filter on the data source
* #param {String} queryText - the search query acquired from the Search Charm
* #return {WinJS.Binding.List} the filtered result of your search query.
*/
_searchData: function (queryText) {
var originalResults;
// window.Data is the data source of the List View
// window.Data is an object defined in YourProject/js/data.js
// at line 16 WinJS.Namespace.define("Data" ...
// Data.items is a array that's being grouped by functions in data.js
if (window.Data) {
// apply a filter to filter the data source
// if you have your own search algorithm,
// you should replace below code with your code
originalResults = Data.items.createFiltered(function (item) {
return (item.termName.indexOf(queryText) >= 0 ||
item.termID.indexOf(queryText) >= 0 ||
item.definition.indexOf(queryText) >= 0);
});
} else {
// if there is no data source, then we return an empty WinJS.Binding.List
// such that the view can be populated with 0 result
originalResults = new WinJS.Binding.List();
}
return originalResults;
}
Since you are thinking about doing the search on your own web service, then you can always make your _searchData function async and make your view waiting on the search result being returned from your web service.
_searchData: function(queryText) {
var dfd = new $.Deferred();
// make a xhr call to your service with queryText
WinJS.xhr({
url: your_service_url,
data: queryText.toLowerCase()
}).done(function (response) {
var result = parseResultArrayFromResponse(response);
var resultBindingList = WinJS.Binding.List(result);
dfd.resolve(result)
}).fail(function (response) {
var error = parseErrorFromResponse(response);
var emptyResult = WinJS.Binding.List();
dfd.reject(emptyResult, error);
});
return dfd.promise();
}
...
// whoever calls searchData would need to asynchronously deal with the service response.
_searchData(queryText).done(function (resultBindingList) {
//TODO: Display the result with resultBindingList by binding the data to view
}).fail(function (resultBindingList, error) {
//TODO: proper error handling
});