angularstrap typeahead with json object array is not working - bootstrap-typeahead

I am using angularstrap typeahead directive. Its working fine with single object json values but its not working when replacing the json with my json object array.
Demo Json:
typeahead= ["Alabama","Alaska","Arizona","Arkansas","California","Colorado","Connecticut","Delaware","Florida","Georgia"];
<input type="text" ng-model="typeaheadValue" bs-typeahead="typeahead">
The above code is working fine.
My JSON object array:
typeahead = [
{id: 1, name: 'name1', email: 'email1#domain.com'},
{id: 2, name: 'name2', email: 'email2#domain.com'},
{id: 3, name: 'name3', email: 'email3#domain.com'}
];
$scope.typeaheadFn = function(query) {
return $.map($scope.typeahead, function(contacts) {
return contacts;
});
}
<input type="text" ng-model="typeaheadValue" bs-typeahead="typeaheadFn">
Please give me some solution for this.

You want to map your items to a list of strings, I believe.
Try:
$scope.typeaheadFn = function(query) {
return $.map($scope.typeahead, function(contact) {
return contact.name;
});
}
(I should add that I am currently stumped by something similar)

If you have, for example:
items = [
{id: 1, name: 'name1', email: 'email1#domain.com'},
{id: 2, name: 'name2', email: 'email2#domain.com'},
{id: 3, name: 'name3', email: 'email3#domain.com'}
];
You will need:
<input type="text" bs-typeahead ng-model="selectedItem" ng-options="item.name for item in items|orederBy:'name'|filter:{name:$viewValue}:optionalCompareFn"></input>
If you exclude filter from ng-options matching will be done on every property of item object, so if you want it to be done on one property add filter:{propName:$viewValue}. Also, if you exclude optionalCompareFn, default comparison from angular will be applied, but you can add your custom one (on your $scope), with signature (actual is property value of the item, stated in filter, not the whole object).
optionalCompareFn(expected,actual){ return /compare and return true or false/}

Attempt 1
I finally got this semi-working after a huge amount of frustration.
An easy way to get your desired text appearing is for each item to have a toString method.
You might have something like
typeaheadData = [
{id: 1, text: "abc", toString: function() { return "abc"; }},
{id: 2, text: "def", toString: function() { return "def"; }}
]
Then you will see the correct text in the options that popup, but the matching won't yet work properly (the items shown by the widget won't match the text the user enters in the box).
To get this working I used the new filter option that's been added in the current git version of angular-strap. Note that it's not even in the pre-built dist/angular-strap.js file in the repository, you will need to rebuild this file yourself to get the new feature. (As of commit ce4bb9de6e53cda77529bec24b76441aeaebcae6).
If your bs-typeahead widget looks like this:
<input bs-typeahead ng-options="item for item in items" filter="myFilter" ng-model="myModel" />
Then the filter myFilter is called whenever the user enters a key. It's called with two arguments, the first being the entire list you passed to the typeahead, and the second being the text entered. You can then loop over the list and return the items you want, probably by checking whether the text matches one or more of the properties of an item. So you might define the filter like this:
var myApp = angular.module('myApp', ['mgcrea.ngStrap'])
.filter('myFilter', function() {
return function(items, text) {
var a = [];
angular.forEach(items, function(item) {
// Match an item if the entered text matches its `text` property.
if (item.label.indexOf(text) >= 0) {
a.push(item);
}
});
return a;
};
});
Unfortunately this still isn't quite right; if you select an item by clicking on it, then the text parameter will be the actual object from the items list, not the text.
Attempt 2
I still found this too annoying so I made a fork of angular-strap (https://github.com/amagee/angular-strap) which lets you do this:
typeaheadData = [
{id: 1, text: "abc"},
{id: 2, text: "def"}
]
//...
$scope.myFormatter = function(id) {
if (id == null) { return; }
typeaheadData.forEach(function(d) {
if (d.id === id) {
return d.text;
}
});
};
<input bs-typeahead ng-options="item for item in items" ng-model="myModel"
key-field="id" text-field="text" formatter="myFormatter" />
With no need to fuss around with toString methods or filters. The text-field is what the user sees, and the key-field is what is written to the model.
(Nope! Still doesn't work if you update the model without going through the view).
The formatter option is needed so when you set the model value to just the key field, the widget can figure out the right text to display.

Related

How to update an array of objects in firestore? [duplicate]

I have a document in Firebase Firestore that is something like the below. The main point here is that I have an array called items with objects inside it:
{
name: 'Foo',
items: [
{
name: 'Bar',
meta: {
image: 'xyz.png',
description: 'hello world'
}
},
{
name: 'Rawr',
meta: {
image: 'abc.png',
description: 'hello tom'
}
}
]
}
I am trying to update a field inside the item array, under the meta object. For example items[0].meta.description from hello world to hello bar
Initially I attempted to do this:
const key = `items.${this.state.index}.meta.description`
const property = `hello bar`;
this.design.update({
[key]: property
})
.then(() => {
console.log("done")
})
.catch(function(error) {
message.error(error.message);
});
This didn't appear to work though, as it removed everything in the item index I wanted to modify, and just kept the description under the meta object
I am now trying the following which basically rewrites the whole meta object with the new data
const key = `items.${this.state.index}.meta`
const property = e.target.value;
let meta = this.state.meta;
meta[e.target.id] = property;
this.design.update({
[key]: meta
})
.then(() => {
this.setState({
[key]: meta
})
})
.catch(function(error) {
message.error(error.message);
});
Unfortunately though, this seems to turn my whole items array into an object that looks something like:
{
name: 'Foo',
items: {
0: {
name: 'Bar',
meta: {
image: 'xyz.png',
description: 'hello world'
}
},
1: {
name: 'Rawr',
meta: {
image: 'abc.png',
description: 'hello tom'
}
}
}
}
Any ideas how I can just update the content I want to?
Firestore doesn't have the ability to update an existing element in an indexed array. Your only array options for updates are described in the documentation - you can add a new element to the array ("arrayUnion") or remove an element ("arrayRemove").
As an alternative, you can read the entire array out of the document, make modifications to it in memory, then update the modified array field entirely.
You can make a separate collection for that particular array, like this in this picture earlier I had different fields (no collections) of name, email and pages, And in this, I wanted to change the data of a specific page that is inside the array. For that, I made a different collection of pages with individual documents of each page having values of title description and content which can be mutated.

Meteor field selection based on criteria

I have a collection that contains posts.
The post document contains a blurred and none-blurred image (blurredImage and image key in the document).
If the current user is the owner of the post or the current user has purchased the content the image key can be pushed to the client.
When this is not the case the image key cannot be pushed (because of security). Otherwise anyone could just query the local mongo and see the full image url, while they didn't pay for it.
Now my question is. What is the best way of doing this kind of field selection in a publish method?
I don't see a way to do a conditional field selection.
Anyone any ideas?
In your publication:
const user = Meteor.users.findOne(this.userId);
if (user.hasPermissionToViewImage) return Documents.find({}, { fields: image: 1, *everything else you need* });
return Documents.find({}, { fields: image: 0, *everything else you need* });
This is actually a bit tricky because the conditional needs to be evaluated on each image based on another field (the ownerId). However you can return an array of cursors in your publication, each with its own query criteria and field filters. In your case:
Meteor.publish('myPublication',()=>{
const ownedByMe = Posts.find({ ownerId: this.userId },{ fields: { image: 1, foo: 1, bar: 1 }});
const notOwnedByMe = Posts.find({ ownerId: { $ne: this.userId }},{ fields: blurredImage: 1, foo: 1, bar: 1 }});
return [ownedByMe,notOwnedByMe];
});
where foo and bar are keys that you want to return in either case.
#sdybskiy Note that you can't mix field inclusion and exclusion in one query. Inclusion implies that all other fields are excluded and vice-versa.

How to show data from mongoDB with ObjectID

i have an "back end" application which write in MongoDb (in database i have _id: with ObjectId("13f6ea...002")) i use meteor app to show information. Everything was good i displays list of information with {{#each}}. But when i wanted show one element with '_Id' nothing works.
I read this issue and adapt my code to get right root, But i can't display anything on the page. I tried to write Template helpers but it didn't helped
Db record:
{
_id: ObjectId("13f6ea...002"),
url: "foo",
title: "bar",
published: "2014-08-22 03:26:21 UTC",
image: "foo.jpg",
summary: "foo ",
categories: [
"F",
"B"
],
...
}
Route:
this.route('news', {
path: '/news/:_id',
template: 'news',
waitOn: function () {
var id = this._id;
Meteor.subscribe('news', id);
},
data: function() {
var id = this.params._id;
return News.findOne({ _id: Meteor.Collection.ObjectID(this.params._id)});
},
action : function () {this.render();},
});
Publish
Meteor.publish('news', function(id) {
return News.find({_id: id});
});
Template which redirect to unique post
<h4>{{title}}</h4>
And template is just {{news}}
How can i fix this?
UPDATE
My solutions to fix that:
router.js
waitOn: function () {
var id = this._id;
Meteor.subscribe('News', id);
},
data: function() {
return News.findOne(new Meteor.Collection.ObjectID(this.params._id));
},
and in template
<a href="news/{{_id._str}}">
Navigate to the appropriate url in your browser (i.e. localhost:3000/news/[_id]), open the console and enter:
Router.current().data()
That will show you the data context of the current route. Either it returns nothing, in which case there is a fundamental problem with your News.findOne query as it's returning nothing, or (more likely) it returns the required document.
In the latter case, as far as I can see there is no news property within that document, which is why it isn't rendering anything. If you change {{news}} to {{url}} or {{summary}} I would imagine it would render the requested property.
If by {{news}} you're trying to render the entire document, then (aside from the fact that it will render as something like [Object]) you need to make news a property of the object returned by your data function:
return {
news: News.findOne({ _id: Meteor.Collection.ObjectID(this.params._id)});
};
Getting Document with _id :
In the .js file under events, Say on click event and Collection EventList :-
'Submit form' : function () {
var id = this._id;
return EventList.find({_id : id}).fetch();
}
This would return the object for the id. In my Case, I am displaying a field for all documents in Collection. User selects a record and clicks Submit, which fetches all Document fields and displays to the User

Password Validation on Dynamically Generated Form

So I'm just learning Angular and I have basic routing setup and a partial for setting up a basic page with a form (theoretically any form), and based on the controller I load it loads that form from a fields array in the controller.
One of those forms is a registration form where I want to verify that the passwords match.
So in the partial I have (a mode complicated version of) this
<input ng-repeat="field in fields" ng-model="field.model" mustEqual="fields.mustEqual">
I found a directive which does password comparison:
taskDivApp.directive('mustEqual', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, elem, attrs, ngModel)
{
if(!ngModel) return; // do nothing if no ng-model
// watch own value and re-validate on change
scope.$watch(attrs.ngModel, function()
{
validate();
});
// observe the other value and re-validate on change
attrs.$observe('equals', function (val)
{
validate();
});
var validate = function()
{
// values
var val1 = ngModel.$viewValue;
var val2 = attrs.mustEqual;
// set validity
ngModel.$setValidity('equals', val1 === val2);
};
}
}
});
So basically all I want to do is be able to load a literal string into ng-model from the controller so that I can pair up the model and the must equal, so the data for the form would look like:
$scope.fields = [
{
'type':"Email",
'placeholder':"Email Address",
'req': true,
'focus':true
},
{
'type':"Password",
'placeholder':"Password",
'model': "password",
'mustEqual': "passwordConf"
},
{
'type':"Password",
'placeholder':"Comfirm Password",
'model':"passwordConf",
'mustEqual': "password"
}
];
However what happens now is that the main password field gets bound to the "model" field of its index in the array and similar for passwordConf (ie inital values literally "password" and "passwordConf")
The fact that this isn't easy makes me think I have the wrong mindset - is this not a good way to do forms, should I just have them hard coded?
If this is okay then any ideas on how I could accomplish it would be appreciated!

Multiple entry select2 and angular model fetched by $resource

I am having some difficulty figuring out how to make it all work together. Here is what I would like to do:
The model is fetched using $resource from the rest API:
var itemResource = $resource('http://blabla.com/items/:id');
$scope.item = itemResource.get({id: '12345'});
The returned item has some fields among which is one array field that lists the ids of the categories:
{
"item_name: "some value",
"categories": ["cat_id1", "cat_id7", "cat_id8"]
}
In the UI I want these categories to be shown as editable multi select. The user should not operate using ids, but rather he should see and be able to chose string representations which come from the mapping within the application. So in html:
<input type"text" ui-select2="categoryOptions" ng-model="item.categories" />
and also in controller:
var categoryMapping = [
{id: "cat_id1", text: "CategoryAlpha"},
...
{id: "cat_id8", text: "CategoryOmega"},
...
];
$scope.categoryOptions = {
'multiple': true,
'placeholder': 'Chose categories',
'width': 'element',
'data': categoryMapping,
};
Obviously the pieces of code above are not working and I don't know how to make them work to do what I want. ui-select2 wants the model (item.categories) to be an array of objects {id, text} and I want it to store only the ids in the items in the database and have the mapping separate. I can't be the first one to do it, there must be a solution, please help.
Thanks