I am trying to implement a custom validation function which can return either true (if the field is valid) or some custom error message. Here's my current attempt:
global.Messages = Models.Messages = new Mongo.Collection 'messages'
MessagesSchema = new SimpleSchema({
content: {
type: String,
label: "Message",
max: 200,
custom: ->
if #obj.content.includes("a")
true
else
"not contain a"
}, {tracker: Tracker})
Messages.attachSchema MessagesSchema
This is a contrived example but still, it's not working. The conditional in the custom function is run, and when true gets returned then the record does save. However, if "not contain a" gets returned, it does not become the validation message displayed on the client. It just says content is invalid, and I'm not sure how to customize this message. Here's the template code:
{{#autoForm collection="Messages" id="insertMessageForm" type="insert"}}
<fieldset>
<legend>Add message</legend>
{{> afFieldInput type='text' name='content'}}
{{#if afFieldIsInvalid name='content'}}
<span class="help-block">{{afFieldMessage name='content'}}</span>
{{/if}}
</fieldset>
<button type='submit' class='btn btn-primary'>Insert</button>
{{/autoForm}}
There were a few problems with my original code.
First of all, I didn't specify how I was requiring SimpleSchema but it should be done this way; this uses the new node-simpl-schema package which is what meteor-simple-schema migrated to:
SimpleSchema = require('simpl-schema').default
SimpleSchema.extendOptions(['autoform']);
Validation messages are mapped to keys:
SimpleSchema.setDefaultMessages
messages:
en:
"notA": "doesnt contain a"
The messages and en hashes are necessary for it to be the correct structure.
important point: the return value of custom is not the message that gets displayed on the client. It is a key, pointing to the entry in the default messages object.
For example:
custom: ->
if #obj.content.includes("a")
true
else
"notA"
This will end up showing the message "doesnt contain a"
Related
I am working on a whatsapp clone app using react, firebase
messages, user name, timestamp are recieved from firestore using this code:
<div className="chat__body">
{messages.map(message => (
<p className={`chat__message ${true && "chat__reciever"}`}>
<span className="chat__name">{message.name}</span>
{message.message}
<span className="chat__timestamp">
{new Date(message.timestamp?.toDate())
.toUTCString()}
</span>
</p>
))}
</div>
<div className="chat__footer">
<InsertEmoticonIcon />
<form >
<input value={input} onChange={(event) =>
setInput(event.target.value)} placeholder="Type a message" type="text" />
<button onClick={sendMessage} type="submit">Send </button>
</form>
<MicIcon />
</div>
The input field texts are captured in {sendMessage} which are viewed in console as input value.
//probably sendMessage onClick event is causing this error.
const sendMessage = (event) => {
event.preventDefault();
console.log("You typed ", input);
db.collection('groups').doc(groupId)
.collection(messages).add({
message: input,
name: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setInput('');
}
as the firestore is set, the inputs are set as messages, name and timestamps set from google authentication in collection like this:
When I type on the input field, those inputs are supposed to be showed in chat instead showing error:TypeError: u.split is not a function
You're passing messages as an array, not as a string.
Change your code to
db.collection("groups").doc(groupId).collection("messages").add({
message: input,
name: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
Looking at the error stack the first project file seems to be:
...
at e.collection (...)
at sendMessage (Chat.js:47)
...
Since you supplied the sendMessage definition I will assume that the definition is located in Chat.js. You haven't given us any line numbers to work with, however the line above "at sendMessage" tells us what function/method you called in sendMessage that caused the error. In your case this was a collection call.
Looking at your code:
const sendMessage = (event) => {
event.preventDefault();
console.log("You typed ", input);
db.collection('groups').doc(groupId)
.collection(messages).add({
message: input,
name: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setInput('');
}
You make 2 collection calls. .collection('groups') and .collection(messages). Assuming that db is an Firestore instance the first collection call has the following signature:
collection ( collectionPath : string ) : CollectionReference < DocumentData >
The second collection call is called upon a DocumentReference but has the same signature.
Your first collection call provides a string 'groups' as argument which looks fine. Your second collection call provides messages as the argument, which is probably an array (you call messages.map in your first code block). Since the method is expecting a string it throws an error.
I am accessing an online API and want to use the text value to populate a ngb-typeahead dropdown. There is a working example on the Angular Bootstrap website using Wikipedia, but the returned data from the Wikipedia API is different to the data I am getting from a geocoding API. The data I get is returned in this format:
{
"suggestions": [
{
"text": "23 Queen Charlotte Drive, Aotea, Porirua, Wellington, 5024, NZL",
"magicKey": "dHA9MCNsb2M9NDMwNzcyNzQjbG5nPTMzI2huPTIzI2xicz0xMDk6NDg1NDQwMzU=",
"isCollection": false
},
{
"text": "23 Queen Mary Avenue, Epsom, Auckland, 1023, NZL",
"magicKey": "dHA9MCNsb2M9NDMwNDY4MjUjbG5nPTMzI2ZhPTE0NDE3OTIjaG49MjMjbGJzPTEwOTo0ODU0NDMyNA==",
"isCollection": false
},
I have been trying to access text in response data with the following:
return this.http
.get<any>(GIS_URL, {params: GIS_PARAMS.set('text', term)}).pipe(
map(response => response.suggestions)
);
I have also read the Angular tutorial here on dealing with response data, but the difference in the example is that they are getting an array of Hero's whereas I am getting an object containing an array of suggestions.
The typeahead looks like:
HTML
<fieldset class="form-inline">
<div class="form-group">
<label for="typeahead-http">Search for a wiki page:</label>
<input id="typeahead-http" type="text" class="form-control mx-sm-3" [class.is-invalid]="searchFailed" [(ngModel)]="model" [ngbTypeahead]="search" placeholder="Wikipedia search" />
<small *ngIf="searching" class="form-text text-muted">searching...</small>
<div class="invalid-feedback" *ngIf="searchFailed">Sorry, suggestions could not be loaded.</div>
</div>
</fieldset>
<hr>
<pre>Model: {{ model | json }}</pre>
Full code on StackBlitz is here.
I am new to Angular, so a verbose answer would be great.
You need to specify resultFormatter and inputFormatter on the typeahead input (refer to Typeahead).
Explanation
Your search method in the service returns a list of suggestion Objects which each look like:
{
isCollection: ...
magicKey: ...
text: ...
}
However by default the typeahead control expects a list of strings, hence it displays your objects as [Object object].
You need to tell the typeahead control how to determine a string value from your object, you do this via resultFormatter and inputFormatter.
These inputs take a function, which has the object as an input and the string display value as its output.
formatter below is that function, it will be called for each item displayed in the list. If you expand it to a normal function you can put a breakpoint in it and see it being called in this manner.
Solution
<input id="typeahead-http" ... [inputFormatter]="formatter" [resultFormatter]="formatter"/>
TypeScript file:
formatter = (item:any) => item.text as string;
Updated StackBlitz
https://stackblitz.com/edit/so-typeahead?file=src%2Fapp%2Ftypeahead-http.ts
Follow-up questions
item in the formatter:
Consider:
formatter = (item:any) => item.text as string;
is shorthand for:
function format(item: any){
return item.text as string;
}
They typeahead control/directive iterates the items returned by search(..) and calls this method which each one. The results are displayed in the select list.
map(response => response.suggestions)
The response from the service is an object like:
{ // object
suggestions:
[
{ ..., text: 'Place 1' },
{ ..., text: 'Place 2' }
]
}
That is an object containing a list named suggestions. The typeahead expects a list only, so the map transforms the object containing list => list only.
Does the formatter that you have defined do both input and result?
Yes, as it is assigned to both [inputFormatter] and [resultFormatter] in the template.
Alternative answer
The mapping is done entirely in the service:
return this.http
.get<any>(GIS_URL, {params: GIS_PARAMS.set('text', term)}).pipe(
map(response => response.suggestions.map(suggestion => suggestion.text)),
);
Each response object is mapped to the list of suggestions. Each suggestion is mapped (using JavaScript map) to its text value.
You can use this solution provided you don't need access to any of the other suggestion properties outside of the service.
Been struggling with an autoform to pre-fill the data, especially in hidden fields.
I have tried many things including using autoValue and defaultValue, but autoValue is validated on the server side and I need to grab a value from the page on the client side (the Router current route name) so it fails when getting looked up in the .clean function, and defaultValue takes a value, and won't take a function.
How to pass a value to a form to pre-fill some fields, without showing the field?
So, I posted the question because I have struggled and found the answer and wanted to share.
In the end, you can pass a doc attribute to a form
in the form it looks like:
{{> quickform
collection"mycollection"
id="formid"
type="method"
...
doc=mydoc
}}
and then you need a template helper to create the doc:
Template.myform_template.helper({
mydoc: function() {
return {field1: value1, field2:value2 };
}
})
You don't have to fill all the fields, just the one you want to pre-fill, just like the way the 'update' form works.
In order NOT to show this value in the form, I had tried to use the omitFields attribute, but that does not work as the field in the doc gets removed. so the only way I found was to declare the type of field in the schema as 'hidden' with
mycollection.attachSchema(new SimpleSchema({
field1: {
type: String,
optional: false,
autoform: {
type: "hidden"
}
}
}))
and here you are.
Now, on calling schema.clean(doc) in the method, the value is not really 'validated' since there is nothing to validate against in the schema, so you should validate those values yourself in the method call.
when i need prefill fields for an atoform, i use the AutoFom.hooks and in the template.html file i don't add these fields.
TemplateFile.html
{{#autoForm schema="Schemas.Myschema" id="idForm" resetOnSuccess="true" type="method" meteormethod="server/insertCustomizedTemplate"}}
{{> afQuickField name='Field1'}}
{{> afQuickField name='Field2'}}
{{> afQuickField name='Field3'}}
<button href="" type="submit" class="btn btn-success">
Send
</button>
{{/autoForm}}
TemplateFile.js
AutoForm.hooks({
idForm: {
before: {
method: function(doc) {
doc.Dummyfield1 = 'Harcoded Value';
doc.Dummyfield2 = 'Harcoded Value';
return doc;
}
},
onSuccess: function(formType, result) {
},
onError: function(formType, error) {
}
}
});
I've the following (simplified) SimpleSchema:
EventSchema = new SimpleSchema({
eventType: {
type: String
},
kicker: {
type: String
},
kicker2: {
type: String,
optional: true
}
});
With this I'm using AutoForm to generate a insert form. Here is a simplified version of it:
{{#autoForm schema="EventSchema" type="method" meteormethod="addEvent"}}
{{> afFieldInput name="eventType" options=getSubstitutionEventTypes type="select-radio-inline"}}
{{> afFieldInput name="kicker" type="select" options=this}}
{{> afFieldInput name="kicker2" type="select" options=this}}
{{/autoForm}}
As I'm using this Schema in an other autoForm where I don't have to input "kicker2", I've set this field to be optional. But in the form mentioned above, this field is required too. So how can I override the optional setting for a field in a specific form?
I've already tried the following, but it didn't work (required isn't rendered in the HTML):
{{> afFieldInput name="kicker2" type="select" options=this required="required"}}
Thanks in advance!
You have some tricks to have an optional value depending on situations, a nice one would be to set the optional value on the return of a function, giving something like this:
EventSchema = new SimpleSchema({
eventType: {
type: String
},
kicker: {
type: String
},
kicker2: {
type: String,
optional: function() {
return (! this.isInsert)
}
}
});
So it is optional on update but not on insert (you can use whatever means to customize it).
Another way to have different validation rules between forms is simply by creating a specific schema for a given form and then having autoForm(schema=yourSpecificSchema ... instead of autoForm(collection="Meteor.users". Do not forget to register an helper so your schema is accessible from your form. You can refer on the official documentation for more details: https://github.com/aldeed/meteor-autoform#non-collection-forms
I am making a chat web app.
people can login with facebook and talk (insert message into mongoDB).
showing the text is simple:
javascript:
messages: function () {
return Messages.find({}, {sort: {createdAt: 1}});
}
each message has these attributes:
text: text,
createdAt: new Date(), // current time
owner: Meteor.userId(), // _id of logged in user
username: Meteor.user().profile.name
It works fine, but I want to "style the message differently" depending on
whether the message's owner is equal to currentUser (i.e. whether it's my message or others message)
For example, I want my message to be float:right and others message to be float:left
I am thinking the code probably looks something like this:
{{#if mymsg}}
<div class="msgdiv_my">
<span class="message">{{text}}</span>
</div>
{{else}}
<div class="msgdiv">
<span class="message">{{text}}</span>
</div>
{{/if}}
Where and how to write the mymsg function (which should return True if the message.owner == currentUser, and false otherwise)
You would usually write those checks in a template helper, like this:
Template.myTemplate.helpers({
ownDocument: function (doc) {
return doc.owner === Meteor.userId();
}
});
Then in myTemplate, call your helper like this:
<template name="myTemplate">
{{#if ownDocument text}}
<div class="msgdiv_my">
<span class="message">{{text}}</span>
</div>
{{else}}
<div class="msgdiv">
<span class="message">{{text}}</span>
</div>
{{/if}}
</template>
Although you may want to implement a global "equals" helper to your meteor app's client, as it sadly is not built in Meteor Spacebars yet:
Template.registerHelper('equals',
function(v1, v2) {
return (v1 === v2);
}
);
This way, you would call:
{{#if equals text.owner currentUser._id}}
And get the same result.
KyleMit wrote a lengthy answer for all your equality check needs in Meteor.