Add translation in Twig template from Javascript/Jquery - javascript

I am working on my form validation and I want to display different error message according to the error I get. I am using Silex and Twig with the translator component and balloon.css for the tooltip and it works so far.
Now I want to translate this error message according to the langage of my website and I have some trouble to do what I want.
Here is my code :
My form
<form class="my-form col-12 col-md-5">
<div class="form-group">
<label for="name" class="col-12">
<input type="text" class="form-control color-light-grey" id="name" name="name" aria-describedby="name" placeholder="{{ 'input-name'|trans }}">
</label>
<small>www.mywebsite.com/<span id="input_name_content">______________</span></small>
</div>
<div class="form-group">
<label for="name" class="col-12">
<input type="password" class="form-control color-light-grey" id="mdp" name="mdp" placeholder="{{ 'input-password'|trans }}">
</label>
</div>
<div class="form-group">
<label for="name" class="col-12">
<input type="password" class="form-control color-light-grey" id="confirm_mdp" name="confirm_mdp" placeholder="{{ 'input-confirm'|trans }}">
</label>
</div>
<button id="create_new" type="button" class="btn bg-color-yellow hover-yellow-light">{{ 'create-pixers'|trans }}</button>
</form>
As you can see, I use {{ 'key'|trans }} to translate my content and it's ok.
My JS
I have a function that check my input value and create an error when I meet one, then at the end if my error obj is not empty I display my error. I use this function to do it, it adds some balloon.css attribute so I have what I want :
function displayFormError(error) {
$.each(error, function(key, msg) {
$("input[name='"+key+"']").parent().parent().attr({
"data-balloon-visible": "",
"data-balloon-pos" : "up",
"data-balloon" : msg
});
$("input[name='"+key+"']").parent().addClass("is-wrong").removeClass("is-valid");
});
}
My problem
When I do this in my HTML, it works (I have my tooltip with the empty error message) :
<div class="form-group" data-balloon-visible data-balloon-pos="up" data-balloon="{{ 'empty'|trans }}">
<label for="name" class="col-12">
<input type="text" class="form-control color-light-grey" id="name" name="name" aria-describedby="name" placeholder="{{ 'input-name'|trans }}">
</label>
</div>
But when I use displayFormError(error) with msg = "{{ 'empty'|trans }}" it doesn't works.
I know it's because I do it in JS, but is it possible to add my message as I want from my JS to my Twig template?
I saw this bundle that could help me maybe, but i'd like to find a solution I can do myself if possible: https://github.com/willdurand/BazingaJsTranslationBundle

No, it's impossible to pass your variable from Javascript to Twig(PHP).
I would use https://github.com/willdurand/BazingaJsTranslationBundle to avoid using 2 different system of translation in your application.
This way you will have your javascript and your message in a same file.
If you really want to do it yourself, you can re-invent the wheel : it's just some text being replaced by another. Or you can find a javascript library.

Well, in fact I manage to solve my problem by adding my Javascript in a Twig Block.
So this code in script.js won't work :
function displayFormError(error) {
$.each(error, function(key, msg) {
$("input[name='"+key+"']").parent().parent().attr({
"data-balloon-visible": "",
"data-balloon-pos" : "up",
"data-balloon" : msg // msg = "{{ 'my-key'|trans }}"
});
$("input[name='"+key+"']").parent().addClass("is-wrong").removeClass("is-valid");
});
}
But if you add it in a Twig Block in your "my_view.html.twig" file it will :
{% block ADD_JS_CODE %}
<script type="text/javascript">
$(document).ready(function() {
function displayFormError(error) {
// same code than before
}
});
</script>
{% endblock %}
This way I can use my function that check my form and add message according to the error in the current langage in the page, without any other component, just Silex + Twig + Translator !

Related

(Javascript Issue) getElementById can't get element even though I can see it in Chrome inspector

everyone,
I'm writing a Web Application with Django.
My problem is actually with one of the templates where I created a javascript function to copy information from one form to another.
I'm trying to copy two values from P tags, one of them is working and the other one is not.
So, here's what I have.
This is my first form in which I'm loading a table with contact names and document ids.
Also in this table the first column for each register is a radio button where I'm setting the onchange event to my javascript function 'copyContact()'.
The table is created dynamically for each contact found in DB, and to proper copy the values for the selected radio, I'm also giving an id for each cell dynamically with the contact_id info that I have.
So basically the First Name info of the contact id 15 will have id="first_name_15" and so on.
<div class="row flex-nowrap border-bottom mx-1">
<div class="col-1">
<p class=" my-2"><input class="form-check-input shadow-sm contactRadio" type="radio" name="contactRadio" id="contact{{ contact_contact_id }}" onchange="copyContact(this)" value="{{ contact.contact_id }}"></p>
</div>
<div class="col-3">
<p class="text-wrap my-2" id="first_name_{{ contact.contact_id }}">{{ contact.contact_first_name }}</p>
</div>
<div class="col-4">
<p class="text-wrap my-2" id="last_name_{{ contact.contact_id }}">{{ contact.contact_last_name }}</a></p>
</div>
<!-- div class="col-2">
<p class="text-wrap my-2">{{ contact.get_document_type_display }}</p>
</div-->
<div class="col-4">
<p class="text-wrap my-2" id="doc_no_{{ contact.contact_id }}">{{ contact.document_no }}</p>
</div>
</div>
Then in my second form is where user should type the Customer information that is being created and the contact selected in first form will be associated with created customer. In order to make user's life easier I want to copy contact info from contact to customer by default after radio selection, then user might change it if necessary.
Here's the second form which is based on Django's Form model.
<!-- CUSTOMER FORM -->
<h4>Informações do Cliente:</h4>
<input type="text" hidden="true" name="selected_contact" id="selected_contact">
<div class="row mb-1">
<label class="col-sm-3 col-form-label col-form-label-sm">Cliente:</label>
<div class="col-sm-9">
{{ customer_form.customer_name }}
<!-- input class="form-control form-control-sm shadow-sm" type="text" value="{{ customer.customer_name }}" -->
</div>
</div>
<div class="row mb-1">
<label class="col-sm-3 col-form-label col-form-label-sm">Tipo:</label>
<div class="col-sm-9">
{{ customer_form.customer_type }}
<!-- input class="form-control form-control-sm shadow-sm" type="text" required value="{{ customer.get_customer_type_display }}" -->
</div>
</div>
<div class="row mb-1">
<label class="col-sm-3 col-form-label col-form-label-sm">Documento:</label>
<div class="col-sm-9">
{{ customer_form.document_type }}
<!-- input class="form-control form-control-sm shadow-sm" type="text" value="{{ customer.get_document_type_display }}" -->
</div>
</div>
<div class="row mb-1">
<label class="col-sm-3 col-form-label col-form-label-sm">Número Doc:</label>
<div class="col-sm-9">
{{ customer_form.document_no }}
<!-- input class="form-control form-control-sm shadow-sm" type="text" value="{{ customer.document_no }}" -->
</div>
</div>
<!-- /CUSTOMER FORM -->
{% if customer_form.errors %}
{% for field in customer_form %}
{% for error in field.errors %}
<div class="alert alert-danger p-1">
{{ field|add:': -'|add:error|escape }}
</div>
{% endfor %}
{% endfor %}
{% for error in customer_form.non_field_errors %}
<div class="alert alert-danger p-1">
{{ error|escape }}
</div>
{% endfor %}
{% endif %}
<!-- /CUSTOMER FORM -->
By using the inspector in chrome I could check that the inputs created by Django Framework have ids.
So the two fields that would receive the values from contact have the following ids:
id_customer_name
and
id_document_no
Image with input IDs in chrome Inspector
Then I created the javascript function to copy from first contact form to second customer form.
Here's what I wrote:
<script>
function copyContact(radioItem) {
var fname = document.getElementById("first_name_" + radioItem.value);
var lname = document.getElementById("last_name_" + radioItem.value);
var cname = fname.innerHTML + " " + lname.innerHTML;
var doc = document.getElementById("doc_no_" + radioItem.value);
var cust_name = document.getElementById("id_customer_name");
var cust_doc = document.getElementById("id_document_no");
cust_name.value = cname;
cust_doc.value = doc.innerHTML;
var contact = document.getElementById("selected_contact");
contact.value = radioItem.value;
}
</script>
My function is being called everytime the user selects a new radio button in contact's form.
The problem that I have is that getElementById does not seem to be working for element id_document_no.
When I select the radio, first_name and last_name of the contact selected is concatenated and sent to id_customer_name input correctly, but this is not happening for id_document_no element for some reason that I don't know.
In this line of the function:
var cust_doc = document.getElementById("id_document_no");
Image of both forms executing the function
By changing the function and sending contact's document info to id_customer_name (for debugging purposes only) var cust_doc = document.getElementById("id_customer_name");, I could see that my function is working at least to get document value.
Image of form working with document value
Then I assume the problem is with getElementById, but I'm not sure.
So does anyone know what might be happening here? Which id did I mispelled?
Cause I'm pretty sure is a dumb mistake.
Sorry for the long post.
I still didn't found the issue with my code and why is not working properly.
Anyway, I found a work around for it, which was to refactor the name of the field from 'document_no' to 'document_number'. This includes template, django form and model, which implies also changing the column name in database.
I guess I'll never know the real issue. ¯_(ツ)_/¯

What is the easiest way to make editForm in Angular?

In my database i have many users which has many recipes.
Every recipe has some properties and collection of ingredients.
Below is screenshot
Recipe with all properties
So when user display recipe to edit on page should appear (form) recipe with loaded current data. This is kind of working because i can see the data but i think it's no done good.
I have form which is working fine without array (ingredients). Could you tell me how i should add ingredients to my edit form?
I'd be grateful if you see at my code and give me feedback and hints what i should change.
export class RecipeEditComponent implements OnInit {
#ViewChild('editForm') editForm: NgForm;
recipe: IRecipe;
photos: IPhoto[] = [];
ingredients: IIngredient[] = [];
uploader: FileUploader;
hasBaseDropZoneOver = false;
baseUrl = environment.apiUrl;
currentMain: IPhoto;
constructor(private route: ActivatedRoute, private recipeService: RecipeService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.loadRecipe();
}
loadRecipe() {
this.recipeService.getRecipe(this.route.snapshot.params.id).subscribe(recipe => {
this.recipe = recipe;
this.initializeUploader();
})
}
updateRecipe(id: number) {
this.recipeService.editRecipe(id, this.recipe).subscribe(next => {
this.toastr.success('Recipe updated successfully');
this.editForm.reset(this.recipe);
}, error => {
this.toastr.error(error);
});
}
}
HTML
<div class="container mt-4 border" *ngIf="recipe">
<form #editForm="ngForm" id="editForm" (ngSubmit)="updateRecipe(recipe.id)" >
<h5 class=" text-center mt-2">Recipe details:</h5>
<div class="form-group mt-3">
<label for="city">Name</label>
<input class="form-control" type="text" name="name" [(ngModel)]="recipe.name">
</div>
<div class="form-group">
<app-ingredient-editor [ingredients] = "recipe.ingredients"></app-ingredient-editor>
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
<div class="form-group">
<br>
<p>Add recipes</p>
</div>
<h5 class=" text-center mt-4">Description</h5>
<angular-editor cols=100% rows="6" [placeholder]="'Your description'" [(ngModel)]="recipe.description" name="description"></angular-editor>
</form>
<button [disabled]="!editForm.dirty" form="editForm" class="btn btn-success btn-block mb-5 mt-5">Save changes</button>
</div>
For now it's look like:
Form on page
When i delete ingredient name while changing on the console i have following error:
recipe-edit.component.html:12 ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.
Problem is that part of code:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
But i don't know how to make it working..
How to add add array to template-driven form?
In my case i need to display current ingredients and be able to edit them.
I have tried something like this :
<input class="form-control" type="text" name="ingredient[i].name" [(ngModel)]="ingredient[i].name">
<input class="form-control" type="text" name="ingredient[i].amount" [(ngModel)]="ingredient[i].amount">
But id doesn't work
The problem is that the property name on the form must be defined in order for angular to know which input to update. You're binding name to the same property that the editable model is set to which means the user can edit it and in fact delete it, which isn't good.
The solution is to change it to a unique value that doesn't change. This should work:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="name{{ingredient.id}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="amount{{ingredient.id}}" [(ngModel)]="ingredient.amount">
</div>
</div>
Link to stackblitz showing it working: https://stackblitz.com/edit/angular-10-base-template-q243lw?file=src%2Fapp%2Fapp.component.html
Edit: fixed bug in original post and added link to stackblitz

Form validation using js - Laravel

I'm using Laravel
I have this form items
I get validation from database and I'm using jquery for validation
all inputs feild have name="element[{{$element->id}}]" (get id from database)
I'm trying different method to make validation but it doesn't work
#if($element->type =='text' && $element->is_depend_on=='0')
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="userName">{{$element->label}}</label>
<input id="userName" name="element[{{$element->id}}]" type="text"
class="form-control #if($element->validation=='required')required #endif" placeholder={{$element->placeholder}}>
#if($errors->has('element.'.$element->id))
<div class="error">{{ $errors->first('element.'.$element->id) }}</div>
#endif
</div>
</div>
</div>
#endif
#if($element->type =='textarea' &&$element->is_depend_on=='0')
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="userName">{{$element->label}}</label>
<textarea class="form-control #if($element->validation=='required')required #endif" name="element[{{$element->id}}]"
placeholder={{$element->placeholder}} *="" style="margin: 0px; width: 601px; height: 108px;" spellcheck="false" data-gramm="false"></textarea></div>
#if($errors->has('element.'.$element->id))
<div class="error">{{ $errors->first('element.'.$element->id) }}</div>
#endif
</div>
</div>
#endif
and here is my js code
var form = $("#order");
form.validate({
errorPlacement: function (error, element) {
error.insertBefore(element);
},
rules: {}
});
also I tried this code
$("#order").validate({
rules: {
},
messages: {
}
});
can anyone help me , or Do you have any suggestions ?
just use https://parsleyjs.org/ for form validation on your blade template. Very effective and has a lot of useful API and plugins. Rather than repeating same validation again and again you can make use of this little open source project.

how to concatenate a string in controller side to autofill form?

I have a bootstrap form where user enter an email I want if user doesn't enter #scoops.com it concatenates automatically either form side or controller side
In controller it saves using this $customer->email = $request->email; if want to check at controller side how I can check if user enters #scoops.com at the end of user name. If it is missing then concatenate #scoops.com with user name
<div class="form-group">
<label>Email <span style="opacity: 0.5; font-style: italic; color: red;">(Required)</span></label>
<div class="input-group">
<input type="search" name="email" id="email" autocomplete="off" class="form-control input-lg" placeholder="Enter Email" name="name" required="#scoops.com" / onfocus='tmp=this.value;this.value=""' onblur='if(this.value=="")this.value=tmp'>
<div class="input-group-append">
<span class="input-group-text">#scoops.com</span>
</div>
</div>
<ul id="suggested-emails" class="list-group"></ul>
<span id="error_email"></span>
#if($errors->has('email'))
<div class="alert alert-danger">
{{ $errors->first('email') }}
</div>
#endif
</div>
`
Something like:
$customer->email = $request->email;
if (!preg_match('/^.*#scoops.com$/', $customer->email)) {
$customer->email .= '#scoops.com';
}
The risk is if they put in anything#example.com, you'll then get anything#example.com#scoops.com. It probably needs something additional to check if it looks like an email address already (i.e. has at least an "#" in it).
First of all you will check if #scoop.com is present or not like below:
use Illuminate\Support\Str;
$myString = '#scoop.com';
$contains = Str::contains($request->email, '#scoop.com');
$contains will either true or false
if it's false then concatenate #scoop.com as below
$newString = $request->email.$myString;

Meteor Accounts - Login forms change HTML remove dropdown JS

How do I modify Meteor's Accounts-ui to change the classes and the html tags rendered without re-writing all the accounts-ui logic?
For example I'd like to remove the "dropdown" behavior and just display the form directly in my page.
I read this answer but it doesn't go into detail - it just removes the default CSS. I'd like to go a bit deeper..
I figured this out for another answer, but thought I'd put it in here since it seems like a quicker way to get what you want.
Template.login.rendered = function()
{
Accounts._loginButtonsSession.set('dropdownVisible', true);
};
(Template.login should be Template.yourTemplateWithLoginButtons)
Styling
Remove accounts-ui
meteor remove accounts-ui
Add accounts-ui-unstyled & less
meteor add accounts-ui-unstyled
meteor add less
Finally, add the following file to your project directory & edit it to your viewing pleasure
https://github.com/meteor/meteor/blob/master/packages/accounts-ui/login_buttons.less
More customization
You can edit the accounts-ui package and edit html & js without starting from scratch:
Remove the accounts-ui-unstyled package and add the stuff in the dir below (except package.js & accounts_ui_tests.js) to your project's client dir, add accounts-urls and edit it to fine tune it to your spec.
https://github.com/meteor/meteor/tree/master/packages/accounts-ui-unstyled
Until meteor gives us a way to specify load order
Rename the following files so they load in the correct order
1accounts_ui.js
2login_buttons.html
3login_buttons_single.html
4login_buttons_dropdown.html
5login_buttons_dialogs.html
6login_buttons_session.js
7login_buttons.js
8login_buttons_single.js
9login_buttons_dropdown.js
login_buttons_dialogs.js
If all you want to do is remove the drop down behavior, then it would suffice to just add the accounts-ui-unstyled package and hide the components you don't want visible: e.g.:
.login-link-text { display: none; }
in your CSS to hide the Sign in link.
Here are the relevant id's and classes that you look into styling:
#forgot-password-link
#login-buttons
#login-buttons-password
#login-dropdown-list
#login-email
#login-email-label
#login-email-label-and-input
#login-password
#login-password-label
#login-password-label-and-input
#login-sign-in-link
#signup-link
.accounts-dialog
.additional-link
.additional-link-container
.login-button
.login-button-form-submit
.login-buttons-dropdown-align-left
.login-close-text-clear
.login-close-textClose
.login-form
.login-form-sign-in
.login-link-and-dropdown-list
.login-link-text
.login-password-form
Create your own html template similar to this below. Style it to your taste.
meteor add accounts-password accounts-ui
<template name="login">
<form class="login-form">
<div class="panel panel-default">
<div class="panel-heading">
<h3>Login</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" id="email" placeholder="Email address">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" id="password" placeholder="password">
</div>
</div>
<div class="panel-footer">
<button type="submit" class="btn btn-danger btn-lg">Login</button>
</div>
</div>
</form>
</template>
You can now call Meteor.loginWithPassword in the template event like so:
Template.login.events({
'submit .login-form': function(e) {
e.preventDefault();
var email = e.target.email.value;
var password = e.target.password.value;
Meteor.loginWithPassword(email, password,function(error){
if(error) {
//do something if error occurred or
}else{
FlowRouter.go('/');
}
});
}
});
You can create another form for registration as well.
Just call Accounts.createUser(object, callback);
This answer is a bit coming late but might help.
Create your own html template similar to this below. Style it to your taste.
meteor add accounts-password accounts-ui
<template name="login">
<form class="login-form">
<div class="panel panel-default">
<div class="panel-heading">
<h3>Login</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" id="email" placeholder="Email address">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" id="password" placeholder="password">
</div>
</div>
<div class="panel-footer">
<button type="submit" class="btn btn-danger btn-lg">Login</button>
</div>
</div>
</form>
</template>
You can now call Meteor.loginWithPassword in the template event like so:
Template.login.events({
'submit .login-form': function(e) {
e.preventDefault();
var email = e.target.email.value;
var password = e.target.password.value;
Meteor.loginWithPassword(email, password,function(error){
if(error) {
//do something if error occurred or
}else{
FlowRouter.go('/');
}
});
}
});
You can create another form for registration as well.
Just call Accounts.createUser(object, callback);

Categories