How do I add input controls dynamically using Meteor? - meteor

I have a form in weather that would have had the condition User add as many lines he needs. He clicks a button and an input is added below the other.
I can do this using jQuery, but I would prefer to use the resources of Meteor. Is it possible to do?

Yes it is, here is an example from one of my apps using the underscore package
In the main template:
<template name="ask">
{{#each answerArray}}
{{>answer}}
{{/each}}
<button id="addItem">Add item</button>
</template>
<template name="answer">
<div class="input-group pt10">
<input class="form-control answer" maxlength="30" placeholder="Answer (max 30 chars)" name="answer" />
<span class="input-group-btn">
<button class="btn btn-danger delButton" id="{{id}}" data-id="{{id}}" type="button">Delete</button>
</span>
</div>
</template>
In the js file:
Template.ask.created = function () {
Session.set('action', 'ask');
answerArray = [ //adding at least two items but it could also be empty
{
id: Random.id(), //using this to give an unique id to the control
value: ''
},
{
id: Random.id(),
value: ''
}
];
Session.set('answerArr', answerArray);
}
And the click event:
Template.ask.events = {
'click #addItem': function () {
var answerArray = Session.get('answerArr');
answerArray.push({
id: Random.id() //just a placeholder, you could put any here
});
Session.set('answerArr', answerArray);
}
}
And finally the helper:
Template.ask.helpers({
answerArray: function () {
var answerArray = Session.get("answerArr")
while (answerArray.length < 2) { //i chose to have it between 2 and 6, you can remove these
answerArray.push({
id: Random.id()
})
}
while (answerArray.length > 6) { // maximum
answerArray.pop();
}
Session.set('answerArr', answerArray);
return answerArray;
}
}
This will reactively increase the number of inputs. After that, if you want to process the inputs you could do the following, on a submit form event or button click:
'click #saveQ': function (e) {
e.preventDefault();
var arr = [];
_.each($('.answer'), function (item) {
if ($(item).val() != '')
arr.push({
answer: $(item).val(), //this you customize for your own purposes
number: 0
})
});
And also if you want to delete an input from the page you can use:
Template.answer.events = {
'click .delButton': function (e) {
var thisId = $(e.target).attr("id");
var answerArray = Session.get('answerArr');
var filteredArray = _.filter(answerArray, function (item) {
return item.id != thisId;
});
Session.set('answerArr', filteredArray);
}
}

Related

VUE3 - Select element on mounted() or created() in a custom component without using "event"

i have a custom input component and i want to select it without clicking on it or something so i cant use "event", i want to select it on created() or mounted()
how can i do that?
<template>
<div class="form-control">
<label :for="id">
{{ label }}
</label>
<input
:id="id"
:type="mode"
:value="currentValue"
#input="$emit('update:modelValue', $event.target.value)"
#focus="pullLabel"
#blur="pushLabel"
/>
</div>
</template>
<script>
export default {
created() {
this.test();
},
methods: {
test() {
console.log(this); // -> How could this be like '<input id="title" type="text">'
},
},
};
</script>
I figured it out myself after 6~ hours of work.
The answer is $refs
I sent a "ref" from parent component like this:
<form ref="settingsForm" #submit.prevent="updateConfig">
Then in methods, i selected all the inputs in form like this: (except submit input)
methods: {
...
focusInput() {
const inputs = this.$refs.settingsForm.querySelectorAll(
'input:not([type="submit"])'
); // $refs.settingsForm -> comes from the parent element, you can name "settingsForm" whatever you want
Array.from(inputs).map((e) => {
if (e.value.length > 0) {
e.previousElementSibling.classList.add("clicked-input");
}
});
},
...
}
By the way, the most important thing is, you have to call the function on mounted() method, not on created()

How to provide new buttons based on user selection?

I am trying to provide the user with a button as follows:
<select id="test" class="btn btn-default btn-lg">
<option>Admin</option>
<option>Employee</option>
</select>
and based on the option selected, another button will appear with other choices.
Here is my code:
Template.addingUser.helpers({
userType: function () {
if(t.find('#test').value == "Admin"){
}else{
}
},
});
If am not mistaken, I can use something like:
$('#action-button').html('<a class="btn btn-primary edit_button" href="' + Template.addingUser.__helpers.get('item')().sourceCode + '" onclick="">Download</a>');
In the case of an action-button, what about select option?
Or should I follow different approach to serve my purpose?
Your code could work, but it is not how we should do in Meteor. Instead you should make the selected option value a reactive data source, when it changes you could simple you button by helper:
const selectedValue = new ReactiveVar('default_value');
Template.addingUser.helpers({
userType() {
if (selectedValue.get() === 'Admin') {
// return somehitng
} else {
}
},
});
Template.addingUser.events({
'change #test': function(e) {
selectedValue.set(e.target.value);
},
});
In this example, I use ReactiveVar to make selected option value reactive.
Updated
.js
const selectedValue = new ReactiveVar('default_value');
Template.addingUser.helpers({
isAdmin() {
return selectedValue.get() === 'Admin';
},
isNormalUser() {
return selectedValue.get() === 'User';
},
});
Template.addingUser.events({
'change #test': function(e) {
selectedValue.set(e.target.value);
},
});
.html
<template name="addingUser">
<!-- ... -->
{{#if isAdmin}}
<button>Button for admin</button>
{{/if}}
{{#if isNormalUser}}
<button>Button for normal user</button>
{{/if}}
<!-- .. .-->
</template>

How to conditionally set attribute on HTML-element in Meteor.js/Handlebars.js?

The code below shows one row from a table, and the JavaScript-code to handle the template. The code works, but it sets the disabled-attribute on all the buttons in the table. I only want it for the one button-element that is pushed.
Question: What is the best way to conditionally set the correct element as disabled in Meteor.js?
In my HTML-file:
<template name="userRow">
<tr>
<td>{{ username }}</td>
<td>
<select class="newRole">
{{{optionsSelected globalRoles profile.role }}}
</select>
</td>
<td>
Disabled: {{disabledAttr}}
{{#isolate}}
<button type="button" {{disabledAttr}} class="btn btn-xs btn-primary saveUser">Save</button>
{{/isolate}}
</td>
<td><button type="button" class="btn btn-xs btn-danger deleteUser">Delete</button></td>
</tr>
And in my .js-file:
var newUserRole;
var savedDep = new Deps.Dependency;
var saved;
var disabledDep = new Deps.Dependency;
var disabledAttr = "";
Template.userRow.saved = function () {
savedDep.depend();
return saved;
};
Template.userRow.disabledAttr = function () {
disabledDep.depend();
return disabledAttr;
};
Template.userRow.events({
'change .newRole' : function (event) {
newUserRole = event.target.value;
},
'click .saveUser' : function (event) {
disabledAttr = "disabled";
disabledDep.changed();
Meteor.call('updateUser',
{
userId: this._id,
role: newUserRole
},
function (error, result) {
if (error) {
saved = "NOT saved, try again!";
} else {
saved = "Saved!";
savedDep.changed();
};
});
return false;
}
});
To answer your question:
All of your rows are using the same Dependency object, so when one row changes the object, all the other rows respond.
To fix this you can create a new Dependency object for each row.
For example:
Template.userRow.created = function () {
this.disabledDep = new Deps.Dependency;
};
And update all of your code to use the template's disabledDep instead of the 'global' one. That should solve your problem.
But let's talk about your goal here:
It looks like you want to render your rows differently while saving, until they're confirmed server side.
A cleaner way to do this is to take advantage of Meteor's isSimulation method. For example:
// Put this in a file that will be loaded on both the client and server
Meteor.methods({
add_item: function (name) {
Items.insert({name: name,
unconfirmed: this.isSimulation});
}
});
This example is using inserts, but you can use the same technique for updates.
Now each document in your collection will have an 'unconfirmed' field, which you can use to change your view:
Template.userRow.disabledAttr = function () {
return this.unconfirmed ? "disabled" : "";
};

Publish not updating properly?

I'm having trouble getting Meteor.publish to update in response to a changing form field. The first call to publish seems to stick, so the query operates in that subset until the page is reloaded.
I followed the approach in this post, but am having no luck whatsoever.
Any help greatly appreciated.
In lib:
SearchResults = new Meteor.Collection("Animals");
function getSearchResults(query) {
re = new RegExp(query, "i");
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
}
In client:
Session.set('query', null);
Template.searchQuery.events({
'keyup .query' : function (event, template) {
query = template.find('.query').value
Session.set("query", query);
}
});
Meteor.autosubscribe(function() {
if (Session.get("query")) {
Meteor.subscribe("search_results", Session.get("query"));
}
});
Template.searchResults.results = function () {
return getSearchResults(Session.get("query"));
}
On server:
Meteor.publish("search_results", getSearchResults);
Template:
Search for Animals
<body>
{{> searchQuery}}
{{> searchResults}}
</body>
<template name="searchQuery">
<form>
<label>Search</label>
<input type="text" class="query" />
</form>
</template>
<template name="searchResults">
{{#each results}}
<div>
{{_id}}
</div>
{{/each}}
</template>
Update [WRONG]
Apparently, the issue is that the collection I was working with was (correctly) generated outside of Meteor, but Meteor doesn't properly support Mongo's ObjectIds. Context here and related Stackoverflow question.
Conversion code shown there, courtesy antoviaque:
db.nodes.find({}).forEach(function(el){
db.nodes.remove({_id:el._id});
el._id = el._id.toString();
db.nodes.insert(el);
});
Update [RIGHT]
So as it turns out, it was an issue with RegExp / $regex. This thread explains. Instead of:
function getSearchResults(query) {
re = new RegExp(query, "i");
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
}
At the moment, one needs to do this instead:
function getSearchResults(query) {
// Assumes query is regex without delimiters e.g., 'rot'
// will match 2nd & 4th rows in Tim's sample data below
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: query, $options: 'i'}} ] }, {limit: 10});
}
That was fun.
PS -- The ddp-pre1 branch has some ObjectId functionality (SearchResults = new Meteor.Collection("Animals", {idGeneration: "MONGO"});)
Here's my working example:
UPDATE the original javascript given was correct. The problem, as noted in the comments, turned out to be that meteor doesn't yet support ObjectIds.
HTML:
<body>
{{> searchQuery }}
{{> searchResults}}
</body>
<template name="searchQuery">
<form>
<label>Search</label>
<input type="text" class="query" />
</form>
</template>
<template name="searchResults">
{{#each results}}
<div>
{{id_species}} | {{name}} - {{_id}}
</div>
{{/each}}
</template>
Javascript:
Animals = new Meteor.Collection("Animals");
function _get(query) {
re = new RegExp(query, "i");
console.log("rerunning query: " + query);
return Animals.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
};
if (Meteor.isClient) {
Session.set("query", "");
Meteor.autosubscribe(function() {
Meteor.subscribe("animals", Session.get("query"));
});
Template.searchQuery.events({
'keyup .query' : function (event, template) {
query = template.find('.query').value
Session.set("query", query);
}
});
Template.searchResults.results = function () {
return _get(Session.get("query"));
}
}
if (Meteor.isServer) {
Meteor.startup(function() {
if (Animals.find().count() === 0) {
Animals.insert({name: "panda", is_active: true, id_species: 'bear'});
Animals.insert({name: "panda1", is_active: true, id_species: 'bearOther'});
Animals.insert({name: "panda2", is_active: true, id_species: 'bear'});
Animals.insert({name: "panda3", is_active: true, id_species: 'bearOther'});
}
});
Meteor.publish("animals", _get);
}

MVC Twitter Bootstrap unobtrusive error handling

I've been trying to get MVC Jquery unobtrusive error handling working with twitter bootstrap for some time now. Its got to the point were i'm either going to edit jquery.validate or do some hack and slash on document.ready.
In order to get unobtrusive error handling to work with Bootstrap and MVC I need to make it so the 'error' class it appended to the 'control-group' class. As well as that, the 'error' class is appended to the input.
I was wondering if anyone in the community has already found a solution.
For example
Typical bootstrap markup would be like so...
<div class="control-group">
<label for="Username">Username</label>
<div class="controls">
<input data-val="true" data-val-required="Username is required" id="Username" name="Username" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Username" data-valmsg-replace="true"></span>
</div>
</div>
What should happen, on blur when jquery.validate unobtrusive fires... it would change to the following
<div class="control-group error">
<label for="Username">Username</label>
<div class="controls">
<input data-val="true" data-val-required="Username is required" id="Username" name="Username" type="text" value="" />
<span class="field-validation-valid help-inline" data-valmsg-for="Username" data-valmsg-replace="true"></span>
</div>
</div>
To get this to work on postback/submit you can do the following...
//twitter bootstrap on post
$(function () {
$('span.field-validation-valid, span.field-validation-error').each(function () {
$(this).addClass('help-inline');
});
$('form').submit(function () {
if ($(this).valid()) {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length == 0) {
$(this).removeClass('error');
}
});
}
else {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
}
});
$('form').each(function () {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
});
});
However, on blur it won't work as you'd expect. I don't want to edit the bootstrap CSS, or Jquery.validate files as they will likely roll out an update at some-point.
Would I create a delegate, or a bind to the jquery functions and work from there. This is deep JS code which I'm not familiar with but could with time firefight my way through it.
Does any one know where I'd start with this problem, or know where it is implemented/been discussed?
var page = function () {
//Update that validator
$.validator.setDefaults({
highlight: function (element) {
$(element).closest(".control-group").addClass("error");
},
unhighlight: function (element) {
$(element).closest(".control-group").removeClass("error");
}
});
} ();
Finally, this fixed it for me. I hope this helps other people too...
My final JS ended like so.
$(function () {
$('span.field-validation-valid, span.field-validation-error').each(function () {
$(this).addClass('help-inline');
});
$('form').submit(function () {
if ($(this).valid()) {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length == 0) {
$(this).removeClass('error');
}
});
}
else {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
}
});
$('form').each(function () {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
});
});
var page = function () {
//Update that validator
$.validator.setDefaults({
highlight: function (element) {
$(element).closest(".control-group").addClass("error");
},
unhighlight: function (element) {
$(element).closest(".control-group").removeClass("error");
}
});
} ();
Here's a nice solution...
Add this to your _Layout.cshtml file outside jQuery(document).ready():
<script type="text/javascript">
jQuery.validator.setDefaults({
highlight: function (element, errorClass, validClass) {
if (element.type === 'radio') {
this.findByName(element.name).addClass(errorClass).removeClass(validClass);
} else {
$(element).addClass(errorClass).removeClass(validClass);
$(element).closest('.control-group').removeClass('success').addClass('error');
}
},
unhighlight: function (element, errorClass, validClass) {
if (element.type === 'radio') {
this.findByName(element.name).removeClass(errorClass).addClass(validClass);
} else {
$(element).removeClass(errorClass).addClass(validClass);
$(element).closest('.control-group').removeClass('error').addClass('success');
}
}
});
</script>
Add this inside $(document).ready():
$("span.field-validation-valid, span.field-validation-error").addClass('help-inline');
$("div.control-group").has("span.field-validation-error").addClass('error');
$("div.validation-summary-errors").has("li:visible").addClass("alert alert-block alert-error");
You're good to go.
Code pieces taken from:
Twitter Bootstrap validation styles with ASP.NET MVC
Integrating Bootstrap Error styling with MVC’s Unobtrusive Error Validation
#daveb's answer
In addition to the answer provided by #leniel-macaferi I use the following as my $(document).ready() function:
$(function () {
$("span.field-validation-valid, span.field-validation-error").addClass('help-inline');
$("div.control-group").has("span.field-validation-error").addClass('error');
$("div.validation-summary-errors").has("li:visible").addClass("alert alert-block alert-error");
});
This also sets the "error" class on the control group if server side validation has failed on a form post and formats any validation summary nicely as a bootstrap error alert.
I know this is an oldy, but I thought I'd share my answer to update for Bootstrap 3. I scratched my head for quite sometime, before building on top of the solution given by Leniel Macaferi.
On top of changing the clases to reflect Bootstrap 3, I thought it would be a nice touch to present the user with a glyphicon to represent the state of the field.
(function ($) {
var defaultOptions = {
errorClass: 'has-error has-feedback',
validClass: 'has-success has-feedback',
highlight: function (element, errorClass, validClass) {
var _formGroup = $(element).closest(".form-group");
_formGroup
.addClass('has-error')
.removeClass('has-success');
if (!_formGroup.hasClass("has-feedback")) {
_formGroup.addClass("has-feedback");
}
var _feedbackIcon = $(element).closest(".form-group").find(".glyphicon");
if (_feedbackIcon.length) {
$(_feedbackIcon)
.removeClass("glyphicon-ok")
.removeClass("glyphicon-remove")
.addClass("glyphicon-remove");
}
else {
$("<span class='glyphicon glyphicon-remove form-control-feedback' aria-hidden='true'></span>")
.insertAfter(element);
}
},
unhighlight: function (element, errorClass, validClass) {
var _formGroup = $(element).closest(".form-group");
_formGroup
.removeClass('has-error')
.addClass('has-success');
if (!_formGroup.hasClass("has-feedback")) {
_formGroup.addClass("has-feedback");
}
var _feedbackIcon = $(element).closest(".form-group").find(".glyphicon");
if (_feedbackIcon.length) {
$(_feedbackIcon)
.removeClass("glyphicon-ok")
.removeClass("glyphicon-remove")
.addClass("glyphicon-ok");
}
else {
$("<span class='glyphicon glyphicon-ok form-control-feedback' aria-hidden='true'></span>")
.insertAfter(element);
}
}
};
$.validator.setDefaults(defaultOptions);
$.validator.unobtrusive.options = {
errorClass: defaultOptions.errorClass,
validClass: defaultOptions.validClass,
};
})(jQuery);
Try use this plugin I've made https://github.com/sandrocaseiro/jquery.validate.unobtrusive.bootstrap
What I did differently from the others answers was to override the errorPlacement and success methods from validate.unobtrusive with my own implementations, but without removing the original implementation so nothing will break.
My implementation look like this:
erroPlacement:
function onError(formElement, error, inputElement) {
var container = $(formElement).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
//calling original validate.unobtrusive method
errorPlacementBase(error, inputElement);
if (replace) {
var group = inputElement.parent();
if (group.hasClass('form-group')) {
group.addClass('has-error').removeClass('has-success');
}
group = group.parent();
if (group.hasClass('form-group')) {
group.addClass('has-error').removeClass('has-success');
}
}
}
success:
function onSuccess(error) {
var container = error.data("unobtrusiveContainer");
//calling original validate.unobtrusive method
successBase(error);
if (container) {
var group = container.parent();
if (group.hasClass('form-group')) {
group.addClass('has-success').removeClass('has-error');
}
group = group.parent();
if (group.hasClass('form-group')) {
group.addClass('has-success').removeClass('has-error');
}
}
}
Out of the box I wanted on blur to raise my error validation. I found this wasn't the case with Jquery Unobtrusive. It seemed to work if you had a select input but not on a text type input. To get around this for me, perhaps its clumsy but I used the following.
$(function () {
$("input[type='text']").blur(function () {
$('form').validate().element(this);
});
});
You can change it is just enabled on certain inputs that have a specific css class.
$(function () {
$(".EnableOnblurUnobtrusiveValidation").blur(function () {
$('form').validate().element(this);
});
});
EnableOnblurUnobtrusiveValidation... is a bit of a long name but you get the jist.
Use TwitterBootstrapMvc.
It takes care of unobtrusive validation attributes automatically and all you have to write to get a full control group with label, input and validation is:
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Field)
Good luck!

Resources