I have 2 different controllers accessing the same service. One of the controllers, search.ctrl.js is visible on every page (the search bar at the top of the page) and the other controller is for a specific partial which displays the results of the search in a table.
The problem that I am having is that the service is setting the values properly after the search form is submitted, and those values are passed back to the controller properly, but then when I call $location.path('/'); the values are no longer set in the service.
Whether this is the correct method or not, all I want is to be able to have a search bar on every page which uses the same html/js/css code, and the results of the search can be accessed from a particular page. Feel free to suggest alternative solutions, but my preferred answer would be one that fixes this one because I have read that it is usually best to use services for sharing data between views.
When viewing the following code keep in mind that it is the documents which I am wanting to access from multiple views.
Cheers :)
Controllers
search.ctrl.js
angular.module("app").controller("SearchController",
function($scope, $location, DocumentSearch){
$scope.title = "Some title";
$scope.doctypes = [
{type: "doc", checked: true},
{type: "docx", checked: true},
{type: "xls", checked: true},
{type: "xlsx", checked: true},
{type: "txt", checked: true}];
$scope.checkAll = true;
$scope.searching = false;
$scope.showSearchPanel = function(){
$scope.searching = !$scope.searching;
};
$scope.checkOrUncheckAll = function(checkAll){
$scope.checkAll = checkAll;
setValueOfAllCheckBoxes($scope.checkAll);
};
$scope.someSelected = function (doctypes) {
for (var d in doctypes){
console.log(doctypes[d]);
if (doctypes[d].checked)
return true;
}
return "You must select a checkbox";
};
$scope.search = function(documentTitle, checkAll){
if (atLeastOneCheckBoxIsSelected()){
$scope.error = null;
$scope.documents = DocumentSearch.search(documentTitle,
getSelectedCheckBoxes());
$location.path("home");
} else {
$scope.documents = null;
$scope.error = "Please select at least one file type.";
}
};
function setValueOfAllCheckBoxes(isChecked){
for (var d in $scope.doctypes){
$scope.doctypes[d].checked = isChecked;
}
}
function atLeastOneCheckBoxIsSelected(){
return (getSelectedCheckBoxes().length > 0);
}
function getSelectedCheckBoxes(){
return $scope.doctypes.filter(function(obj){
return obj.checked;
});
}
});
main.ctrl.js
angular.module("app").controller("MainController",
function($scope, $location, DocumentSearch){
$scope.documents = DocumentSearch.documents;
console.log(DocumentSearch);
});
search.ser.js
angular.module("app").factory("DocumentSearch", function(){
var search = function(title, doctypes){
return searchExample(title, doctypes);
};
function getDocumentExtention(checked){
checked.sort( function(){ return 0.5 - Math.random(); } );
return checked[0].type;
}
console.log("SOMETHING");
var documents = searchExample("Example document", [{type: "txt", checked: true}]);
function searchExample(title, doctypes){
documents = [];
for (var i = 0; i < 50; i++) {
documents.push({
documentTitle: title + " " + i + "." + getDocumentExtention(doctypes),
documentID: i * 111111111,
documentStatus: "Available",
documentVersionDate: "15:32:254 18/11/2015",
documentFileModified: "15:35:974 18/11/2015",
documentCheckedOutUserID: "8276",
documentCheckedOutUsername: "joshuaBrass1"
});
}
return documents;
}
return {
search: search,
documents: documents
};
});
Related
I'm trying to get a list of quantities (identified as cantidad) chosen of each product (as id) to my flask controller. Since wtform didn't adapted well to my needs I had to use json and jQuery, but no matter what I do, the list of objects is seen as empty in the controller, even if in the frontend it does has something inside.
This is the JavaScript:
This function posts the order which should be a list of quantities with id assigned:
<script type="text/JavaScript">
function setPedido(order){
console.log(order);
$.ajax({
url: "{{url_for('main.pedido')}}",
type: 'POST',
data: JSON.stringify(order),
contentType: 'application/json;charset=UTF-8',
success: function(response) {
if (response.redirect) {
window.location.href = response.redirect;
}
}
});
};
When "pedido" (order) button is clicked, gets all assigned quantities and their respective product IDs:
$(".btnPedido").click(function(){
var order = []
$(function() {
var lines = $(".btnAgregar.active")
for(var i = 0; i < lines.length; i++) {
console.log(lines[i]);
order.push({
'id': $(lines[i]).closest("tr").find('.btnPedido').val(),
'cantidad' : $(lines[i]).closest("tr").find('.inpCantidad').val()
});
}
});
setPedido(order);
});
This marks each quantity input as ready to order:
$(".btnAgregar").click(function(){
if($(this).val() == 0){
$(this).val(1);
$(this).text("Agregado");
}else{
$(this).val(0);
$(this).text("Agregar");
}
$(this).toggleClass('btn-info btn-success active');
});
</script>
This shows in the console:
And this in flask controller (notice data):
The problem was at this function, paying better attention i declared a function inside another function, wich is executed at a different scope than setPedido(order), at wich point it stills empty.
To make it worse, the console.log inside the inner function where misleading.
$(".btnPedido").click(function(){
var order = []
$(function() {
var lines = $(".btnAgregar.active")
for(var i = 0; i < lines.length; i++) {
console.log(lines[i]);
order.push({
'id': $(lines[i]).closest("tr").find('.btnPedido').val(),
'cantidad' : $(lines[i]).closest("tr").find('.inpCantidad').val()
});
}
});
setPedido(order);
});
The correct way to do it is:
$(".btnPedido").click(function(){
var order = []
var lines = $(".btnAgregar.active")
for(var i = 0; i < lines.length; i++) {
console.log(lines[i]);
order.push({
'id': $(lines[i]).closest("tr").find('.btnPedido').val(),
'cantidad' : $(lines[i]).closest("tr").find('.inpCantidad').val()
});
}
setPedido(order);
});
I am building a website over a database of music tracks. The database is as follows :
music table contains musicid and title
musicrights table contains musicid and memberid
members table contains memberid and memberinfo.
I'm trying to build an array of objects in my database service, which each entry represents a track containing its rightholders (contains information aubout one rightholder but not his name) and their member info (contains name etc). The backend is sailsjs and the code is as follows :
angular.module("myapp").service("database", ["$q", "$http", function($q, $http) {
var database = {};
function getHolderMember(rightHolder) {
return ($http.get("/api/members?where=" + JSON.stringify({
memberid: rightHolder.memberid
})).then(function (res) {
rightHolder.member = res.data[0];
return (rightHolder);
}));
}
function getRightHolders(doc) {
return ($http.get("/api/musicrights?where=" + JSON.stringify({
musicid: doc.musicid
})).then(function(res) {
// array of promises :
// each rightholder of a document has to solve member info
var rightHolders = [];
for (var i in res.data) {
var rightHolder = {
member: res.data[i].memberid,
type: res.data[i].membertype,
rights: res.data[i].memberrights
};
rightHolders.push(getHolderMember(rightHolder));
}
return ($q.all(rightHolders));
}).then(function(rightHolders) {
// expected array of one or two rightholders,
// enriched with member information
// actually returns array of one or two arrays of 30 members
// without rightholder info
console.log(rightHolders);
doc.rightHolders = rightHolders;
return (doc);
}));
}
database.music = function(q) {
return ($http.get("/api/music?where=" + JSON.stringify({
or: [{
title: {
contains: q
}
}, {
subtitle: {
contains: q
}
}]
})).then(function(res) {
// array of 30 promises :
// each one of 30 documents has to resolve its rightholders
var documents = [];
for (var i in res.data) {
documents.push(getRightHolders(res.data[i]));
}
return ($q.all(documents));
}));
}
return (database);
}]);
The first array of promises seems to work as expected, but not the second one in getRightHolders. What is strange is that this function returns an array of one or two promises, which are rightHolders waiting for their memberinfo. But in the callback where I console.log the response, i get an array of one or two (as per the number of pushed promises) but this array's elements are arrays of 30 memberinfo instead of one memberinfo. I don't understand how this $q.all() call gets mixed with the previous-level $q.all.
The data structure is roughly like this
documents [ ] ($http => 30 responses)
music.musicid
music.rightHolders [ ] ($http => 1, 2, 3 responses)
rightholder.rights
rightholder.member ($http => 1 response)
member.memberinfo
Any help appreciated. Thank you !
UPDATE : Thank you for your answer, it worked like a charm. Here's the updated code, with also the migrate service which formats data differently (there is some database migration going on). I kept it out of the first example but your answer gave me this neat syntax.
angular.module("myApp").service("database", ["$q", "$http", "migrate", function($q, $http, migrate) {
var database = {};
function getHolderMember(rightHolder) {
return ($http.get("/api/members?where=" + JSON.stringify({
memberID: rightHolder.member
})).then(function(res) {
return (migrate.member(res.data[0]));
}).then(function(member) {
rightHolder.member = member;
return (rightHolder);
}));
}
function getRightHolders(doc) {
return ($http.get("/api/rightHolders?where=" + JSON.stringify({
musicID: doc.musicID
})).then(function(res) {
return (
$q.all(res.data
.map(migrate.rightHolder)
.map(getHolderMember)
)
);
}).then(function(rightHolders) {
doc.rightHolders = rightHolders;
return (doc);
}));
}
database.music = function(q) {
return ($http.get("/api/music?where=" + JSON.stringify({
or: [{
title: {
contains: q
}
},
{
subtitle: {
contains: q
}
}
]
})).then(function(res) {
return (
$q.all(res.data
.map(migrate.music)
.map(getRightHolders)
)
);
}));
}
return (database);
}
I'm not quite sure how you're getting the result you describe, but your logic is more convoluted than it needs to be and I think this might be leading to the issues you're seeing. You're giving the getRightsHolders function the responsibility of returning the document and based on your comment above, it sounds like you previously had the getHolderMember() function doing something similar and then stopped doing that.
We can clean this up by having each function be responsible for the entities it's handling and by using .map() instead of for (please don't use for..in with arrays).
Please give this a try:
angular
.module("myapp")
.service("database", ["$q", "$http", function($q, $http) {
var database = {};
function getHolderMember(memberId) {
var query = JSON.stringify({ memberid: memberid });
return $http.get("/api/members?where=" + query)
.then(function (res) {
return res.data[0];
});
}
function populateRightsHolderWithMember(rightsHolder) {
return getHolderMember(rightsHolder.memberid)
.then(function (member) {
rightsHolder.member = member;
return rightsHolder;
});
}
function getRightHolders(doc) {
var query = JSON.stringify({ musicid: doc.musicid });
return $http.get("/api/musicrights?where=" + query)
.then(function(res) {
return $q.all(res.data.map(populateRightsHolderWithMember));
});
}
function populateDocumentWithRightsHolders(document) {
return getRightsHolders(document)
.then(function(rightsHolders) {
document.rightsHolders = rightsHolders;
return document;
});
}
database.music = function(q) {
return $http.get("/api/music?where=" + JSON.stringify({
or: [{
title: {
contains: q
}
}, {
subtitle: {
contains: q
}
}]
})).then(function(res) {
return $q.all(res.data.map(populateDocumentWithRightsHolders));
});
}
return (database);
}]);
I'm trying to set a dictionary of changes in order to track any modifcation to the scope when I execute the take snapshot function, however, for some reason, it starts working at the third try, other cases, my current value is sync with the previous, any idea why?
My saving factory looks like :
.factory('AuthoringState', function() {
var track = 0;
var _pool = {};
return {
addChange : function(data) {
track++;
var temp = _.cloneDeep(data);
_pool[track] = temp;
temp['track'] = track;
return _pool[track];
},
undo : function(checklist) {
if(checklist['track'] === 1) {
return checklist;
}
return _pool[checklist['track'] - 1];
},
back : function(checklist) {
if(checklist['track'] === track) {
return checklist;
}
return _pool[checklist['track'] + 1];
}
}
})
and my controller like this:
.controller('Sample', function($scope, AuthoringState) {
var tasks = {
tasks:[
{value: 1, text: 'I\'m a task'},
{value: 2, text: 'I\'m another task'}]
};
var vm = this;
/**Init**/
vm.checklist = new CheckList(tasks);
vm.checklist = AuthoringState.addChange(vm.checklist);
/** Methods**/
$scope.snapshoot = function() {
vm.checklist = AuthoringState.addChange(vm.checklist);
}
$scope.undo = function() {
vm.checklist = AuthoringState.undo(vm.checklist);
}
$scope.back = function() {
vm.checklist = AuthoringState.back(vm.checklist);
}
$scope.check = function() {
console.log($scope.checklist);
}
});
Here is a jsbin to see it working.
By returning the snapshot objects themselves from your service and binding them to the template, you are allowing the user's changes to modify the snapshots. It sounds like you want them to be immutable instead, so a snapshot doesn't change after it's saved.
Instead, clone the snapshots when you restore them and return the clones, leaving the snapshots untouchable:
undo : function(checklist) {
if(checklist.track === 1) {
return checklist;
}
return _.cloneDeep(_pool[checklist.track - 1]);
},
back : function(checklist) {
if(checklist.track === track) {
return checklist;
}
return _.cloneDeep(_pool[checklist.track + 1]);
}
Also, when saving a new snapshot, don't replace the current checklist object on the scope with the snapshot from the pool (which makes the snapshot itself editable), all you need to do is update its track property:
addChange : function(data) {
track++;
var temp = _.cloneDeep(data);
_pool[track] = temp;
temp.track = track;
data.track = track;
},
And in the controller:
vm.checklist = new CheckList(tasks);
AuthoringState.addChange(vm.checklist);
$scope.snapshoot = function() {
AuthoringState.addChange(vm.checklist);
}
Combining these changes, we get this working version: http://jsbin.com/juwimepofi/1/edit?js,output
(was not sure what to have as a title, so if you have a better suggestion, feel free to come up with one - I will correct)
I am working on an angular application where I have some menues and a search result list. I also have a document view area.
You can sort of say that the application behaves like an e-mail application.
I have a few controllers:
DateCtrl: creates a list of dates so the users can choose which dates they want to see posts from.
SourceCtrl: Creates a list of sources so the user can choose from which sources he/she wants to see posts from.
ListCtrl: The controller populating the list. The data comes from an elastic search index. The list is updated every 10-30 seconds (trying to find the best interval) by using the $interval service.
What I have tried
Sources: I have tried to make this a filter, but a user clicks two checkboxes the list is not sorted by date, but on which checkbox the user clicked first.
If it is possible to make this work as a filter, I'd rather continue doing that.
The current code is like this, it does not do what I want:
.filter("bureauFilter", function(filterService) {
return function(input) {
var selectedFilter = filterService.getFilters();
if (selectedFilter.length === 0) {
return input;
}
var out = [];
if (selectedFilter) {
for (var f = 0; f < selectedFilter.length; f++) {
for (var i = 0; i < input.length; i++) {
var myDate = input[i]._source.versioncreated;
var changedDate = dateFromString(myDate);
input[i]._source.sort = new Date(changedDate).getTime();
if (input[i]._source.copyrightholder === selectedFilter[f]) {
out.push(input[i]);
}
}
}
// return out;
// we need to sort the out array
var returnArray = out.sort(function(a,b) {
return new Date(b.versioncreated).getTime() - new Date(a.versioncreated).getTime();
});
return returnArray;
} else {
return input;
}
}
})
Date: I have found it in production that this cannot be used as a filter. The list of posts shows the latest 1000 posts, which is only a third of all posts arriving each day. So this has to be changed to a date-search.
I am trying something like this:
.service('elasticService', ['es', 'searchService', function (es, searchService) {
var esSearch = function (searchService) {
if (searchService.field === "versioncreated") {
// doing some code
} else {
// doing some other type of search
}
and a search service:
.service('searchService', function () {
var selectedField = "";
var selectedValue = "";
var setFieldAndValue = function (field, value) {
selectedField = field;
selectedValue = value;
};
var getFieldAndValue = function () {
return {
"field": selectedField,
"value": selectedValue
}
};
return {
setFieldAndValue: setFieldAndValue,
getFieldAndValue: getFieldAndValue
};
})
What I want to achieve is this:
When no dates or sources are clicked the whole list shall be shown.
When Source or Date are clicked it shall get the posts based on these selections.
I cannot use filter on Date as the application receives some 3000 posts a day and so I have to query elastic search to get the posts for the selected date.
Up until now I have put the elastic-search in the listController, but I am now refactoring so the es-search happens in a service. This so the listController will receive the correct post based on the selections the user has done.
Question is: What is the best pattern or method to use when trying to achieve this?
Where your data is coming from is pretty irrelevant, it's for you to do the hook up with your data source.
With regards to how to render a list:
The view would be:
<div ng-controller='MyController as myCtrl'>
<form>
<input name='searchText' ng-model='myCtrl.searchText'>
</form>
<ul>
<li ng-repeat='item in myCtrl.list | filter:myCtrl.searchText' ng-bind='item'></li>
</ul>
<button ng-click='myCtrl.doSomethingOnClick()'>
</div>
controller would be:
myApp.controller('MyController', ['ElasticSearchService',function(ElasticSearchService) {
var self = this;
self.searchText = '';
ElasticSearchService.getInitialList().then(function(list) {
self.list = list;
});
self.doSomethingOnClick = function() {
ElasticSearchService.updateList(self.searchText).then(function(list) {
self.list = list;
});
}
}]);
service would be:
myApp.service('ElasticSearchService', ['$q', function($q) {
var obj = {};
obj.getInitialList = function() {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
obj.updateList = function(param) {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
return obj;
}]);
This code has NOT been tested but gives you an outline of how you should approach this. $q is used because promises allow things to be dealt with asynchronously.
I am currently working on an app using firebase and angularJS (ionic). Basically this is a car management app, so you have people sharing their cars with others. I tried to structure the data as flat as possible to be efficient. My issue here is that if without problem I can display the list of the car_id of the different cars shared with the logged user, I can't find a way to display the list of cars shared with the user displaying the year and the model.
Thank you in advance for your help !
{
"rules": {
"users": {
".write": true,
"$uid": {
".read": "auth != null && auth.uid == $uid"
},
"cars": {
"car_id":true,
"role":true // Owner, borower...
}
},
"cars": {
"car_id":true,
"model":true,
"year":true
}
}
}
carapp.controller("carsController", function($scope, $firebaseObject, $ionicPopup, $ionicHistory) {
$ionicHistory.clearHistory();
$scope.list = function() {
frbAuth = frb.getAuth();
if(frbAuth) {
var userObject = $firebaseObject(frb.child("users/" + frbAuth.uid));
userObject.$bindTo($scope, "user");
$scope.cars = frb.child("cars");
}}
$scope.createCar = function() {
$ionicPopup.prompt({
model: 'Create a new car',
inputType: 'text'
})
.then(function(result) {
if(result !== "") {
var newCar = $scope.cars.push({
model: result
})
var newCarId = newCar.key();
$scope.user.cars.push({car_id: newCarId, role: "owner" });
} else {
console.log("Action not completed");
}
});
}
});
<div class="list">
<a ng-repeat="car in user.cars" >
<h2>{{car.car_id}}</h2> ----> works fine !
<h2>{{car.model}}</h2> ----> How to get this working ?
<h2>{{car.year}}</h2> ----> How to get this working ?
</a>
</div>
In the users/ path, begin by storing the list of cars by index, instead of in an array. So your structure would be:
{
"users": {
"kato": {
"cars": {
"DeLorean": true
}
}
},
"cars": {
"DeLorean": {
model: "DeLorean",
year: "1975"
}
}
}
To join this using AngularFire, you have several approaches available. An AngularFire-only solution might look like this, taking advantage of $extend:
app.factory('CarsByUser', function($firebaseArray) {
return $firebaseArray.$extend({
$$added: function(snap) {
return new Car(snap);
},
$$updated: function(snap) {
// nothing to do here; the value of the index is not used
},
$$removed: function(snap) {
this.$getRecord(snap.key()).destroy();
},
// these could be implemented in a manner consistent with the
// use case and above code, for simplicity, they are disabled here
$add: readOnly,
$save: readOnly
});
var carsRef = new Firebase(...).child('cars');
function Car(snap) {
// create a reference to the data for a specific car
this.$id = snap.key();
this.ref = carsRef.child(this.$id);
// listen for changes to the data
this.ref.on('value', this.updated, this);
}
Car.prototype.updated = function(snap) {
this.model = data.model;
this.year = data.year;
}
Car.prototype.destroy = function() {
this.ref.off('value', this.meta, this);
};
function readOnly() { throw new Error('This is a read only list'); }
});
app.controller('...', function($scope, CarsByUser, authData) {
// authenticate first, preferably with resolve
var ref = new Firebase(...).child(authData.uid);
$scope.cars = CarsByUser($scope);
});
For a more sophisticated and elegant approach, one could utilize NormalizedCollection and pass that ref into the AngularFire array:
app.controller('...', function($scope, $firebaseArray) {
var ref = new Firebase(...);
var nc = new Firebase.util.NormalizedCollection(
ref.child('users/' + authData.uid),
ref.child('cars')
)
.select('cars.model', 'cars.year')
.ref();
$scope.cars = $firebaseArray(nc);
});