Knockout Checkbox does not update UI when clicked, but viewModel is updated - javascript

I have a set checkboxes:
<div class="panel panel-default" style="margin-right:10px;">
<div class="panel-heading">
<span class="glyphicon glyphicon-cog"> Configurações Abreviar</span>
</div>
<div class="panel-body">
<div class="row" style="margin-bottom:10px">
<div class="col-lg-2">
<span><strong>Nome</strong></span>
</div>
<div class="col-lg-10">
<div class="btn-group btn-group-sm well" data-toggle="buttons">
<input type="checkbox" data-bind="checked: AbreviaNome" id="AbreviaNome">
<input type="checkbox" data-bind="checked: AbreviaFantasia" id="AbreviaFantasia">
</div>
</div>
</div>
<div class="row" style="margin-bottom:10px">
<div class="col-lg-2">
<span><strong>Endereço</strong></span>
</div>
<div class="col-lg-10">
<div class="btn-group btn-group-sm well" data-toggle="buttons">
<input type="checkbox" data-bind="checked: AbreviaLogradouro" id="AbreviaLogradouro">
<input type="checkbox" data-bind="checked: AbreviaComplemento" name="type" id="AbreviaComplemento">
<input type="checkbox" data-bind="checked: AbreviaBairro" id="AbreviaBairro">
<input type="checkbox" data-bind="checked: AbreviaCidade" id="AbreviaCidade">
</div>
</div>
</div>
<div class="row" style="margin-bottom:10px">
<div class="col-lg-2">
<span><strong>Extra</strong></span>
</div>
<div class="col-lg-10">
<div class="btn-group btn-group-sm well" data-toggle="buttons">
<input type="checkbox" data-bind="checked: AbreviaExtra" id="AbreviaExtra">
</div>
</div>
</div>
</div>
And the following ViewModel:
function JobDetailsViewModel() {
var self = this;
var baseUri = '/Api/Pedidos/';
self.AbreviaNome = ko.observable(false);
self.AbreviaFantasia = ko.observable(false);
self.AbreviaLogradouro = ko.observable(false);
self.AbreviaComplemento = ko.observable(false);
self.AbreviaBairro = ko.observable(false);
self.AbreviaCidade = ko.observable(true);
self.AbreviaExtra = ko.observable(true);
var updatableData = {
AbreviaNome: self.AbreviaNome,
AbreviaFantasia: self.AbreviaFantasia,
AbreviaLogradouro: self.AbreviaLogradouro,
AbreviaComplemento: self.AbreviaComplemento,
AbreviaBairro: self.AbreviaBairro,
AbreviaCidade: self.AbreviaCidade,
AbreviaExtra: self.AbreviaExtra
};
self.update = function (formElement) {
alert('BOOM');
$.ajax({
type: "PUT",
url: baseUri,
data: ko.toJSON(updatableData),
dataType: "json",
contentType: "application/json"
})
.done(function (data) {
})
.error(function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
alert("fail");
});
};
}
$(document).ready(function () {
var viewModel = new JobDetailsViewModel();
ko.applyBindings(viewModel);
viewModel.AbreviaNome.subscribe(function (newValue) {
alert(newValue);
viewModel.update();
});
});
It seem that it works just fine, the subscription works, the value is being updated, and the PUT resquest is being sent, and no console errors.
But the checkbox UI does not change its state to reflect the Model when I click it. Any checkbox I add within the panel does not work, they have the same behavior, even when they are not bound to the view model.
<input type="checkbox" name="type">
What is wrong with my code, should I be doing an array or something like that, why cant I just use simple properties and bind them where I want?
I have fiddle with the formatted code: JSFIDDLE

This has nothing to do with Knockout. It's caused by Bootstrap. When you include data-toggle="buttons", Bootstrap intercepts the click event to update the Bootstrap buttons. But you don't actually have any buttons, which causes the event to just be cancelled.
The easiest fix is to just remove data-toggle="buttons" as I've done to the last checkbox in your example: http://jsfiddle.net/mbest/RK96d/2/
Reference: http://getbootstrap.com/javascript/#buttons

According to all the HTML specs, the name and value attributes for checkboxes are required. It's possible that some browsers act strangely when they aren't supplied.

Related

Setting Events for similar fields in HTML using JQuery, and Javascript

I am not really good at the HTML world, and I'm not even sure how to debug this one. Anyway, I have an ASP.NET core App. My issue is in a CSHTML view. It is a timeclock system. User logs time against an existing job.
I have an Index.cshtml that is working. It will verify a JobNumber to make sure it exists in the database. And if the user enters a partial number and hits F3, it pops up a modal window (I'm using Bootstrap 5) to allow them to select from a list.
The problem is, the user wants to add more Job numbers. So, they can clock time against up to five Jobs at once. So, I am creating new fields, and naming them JobNumber2, JobNumber3, etc.
What I want to do is reuse the existing scripts to add the verification and popup functionality to each of the new fields.
I have tried several different things based on a half a dozen tutorials out there, but I am just not good enough at Javascript and JQuery to know how to do this.
Any help is appreciated!
[EDIT]
Ruikai Feng's answer shows how to match the first function, but that one calls validateJobNumber(jobNumber), and the result will update a field -- again based on the same pattern. So, now it updates: jobNumberValidationMessage -- but I need it to update the correct jobNumberValidationMessage depending on which JobNumber field got matched in the first half of this. IDK, maybe these could be combined into one function? I'm not sure. But how do I take what I matched with id^='JobNumber to figure out which jobNumberValidationMessage to update (ie jobNumberValidationMessage2, jobNumberValidationMessage3, etc) ;
------------ END EDIT
Here's the code I have that is working, but needs changed:
#using Microsoft.AspNetCore.Http
#using Microsoft.AspNetCore.Http.Extensions
#model dynamic
<!DOCTYPE html>
<html>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-10">
<!-- Clock-In Header -->
<h3>
<img class="logo large" src="logo-png-transparent.png")"
alt="Logo" width="100" height="100"> Add Job Number(s) to Track Time for: #Model.Employee.Name
</h3>
<hr /> <!-- Ruler Line for Separation -->
<!-- End Clock-In Header -->
<!-- Clock-In Form -->
<div class="row">
<div class="col-1 col-md-12 offset-md-0">
<div class="card">
<div class="card-body">
<form asp-action="ClockInBegin" method="post">
<label for="JobNumber" class="col-7 col-md-2 col-form-label text-md-right">Job Number</label>
<div class="col-md-4">
<input type="text" id="JobNumber" name="JobNumber" class="form-control" onkeydown="jobNumberKeyDown(this)" onblur="jobNumberBlur(this)" value="#Model.TrackingItem.JobNumber">
<div class="col-md-8">
<span id="jobNumberValidationMessage"></span>
</div>
</div>
</div>
<div class="form-group row">
<div class="form-check form-switch col-4 align-with-label">
<input class="form-check-input" type="checkbox" value="" id="MultipleCheck">
<label class="form-check-label" for="MultipleCheck">Multiple</label>
</div>
</div> <!-- End form-group row -->
<div>
<button type="submit" class="btn btn-primary w-100">Start Clock</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Clock-In Modal Pop-up -->
<div class="modal fade" id="myModal">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Select Job Number</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<div class="modal-body">
<select id="jobNumberSelect" class="form-control">
<option value="">Select Job Number</option>
<!-- Dynamic options generated via JavaScript or ajax -->
</select>
</div>
<div class="modal-footer">
<button type="button" id="CANCEL"class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="OK" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
<!-- End Clock-In Modal Pop-up -->
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$("#JobNumber").blur(function () {
var jobNumber = $(this).val();
validateJobNumber(jobNumber);
});
$("#JobNumber").keydown(function (event) {
if (event.key === "F3") {
event.preventDefault();
if (event.target.value.length >= 2) {
// Open the modal
$('#myModal').modal('show');
// Populate the select options
$.ajax({
type: "GET",
url: "#Url.Action("GetJobNumbers")",
data: { searchTerm: event.target.value },
dataType: "json",
success: function (data) {
$("#jobNumberSelect").empty();
$.each(data, function (index, item) {
$("#jobNumberSelect").append("<option value='" + item + "'>" + item + "</option>");
});
$("#jobNumberSelect").val("..."); // clear the initial value. Make them select it
//set prompt in first cell of select
$("#jobNumberSelect").prepend("<option value=''>Select Job Number</option>");
$("#myModal").modal("show");
}
});
}
}
});
$("#jobNumberSelect").change(function () {
$("#JobNumber").val($(this).val());
});
$("#OK").click(function () {
$("#JobNumber").val($("#jobNumberSelect").val());
validateJobNumber(); // call the validation
$("#myModal").modal("hide");
});
$('#MultipleCheck').change(function () {
if (this.checked) {
$(this).val(true);
$('[name="MultipleCheck"]:hidden').val(true);
$("#hiddenFields").show();
}
else {
$(this).val(false);
$("#hiddenFields").hide();
}
})
}); // end Document.Ready functions
function validateJobNumber() {
var jobNumber = $("#JobNumber").val();
$.ajax({
type: "POST",
url: "#Url.Action("VerifyJobNumber")",
data: { jobNumber: jobNumber },
dataType: "text",
success: function (respdata) {
// alert(respdata);
const obj = JSON.parse(respdata);
var rmessage = obj.message;
$("#jobNumberValidationMessage").text(rmessage);
$("#jobNumberValidationMessage").css("color", "green");
}
});
}
</script>
</body>
</html>
if you have mutipule inputs like:
<input type="text" id="JobNumber1" name="JobNumber1" class="form-control"  value="1">
<input type="text" id="JobNumber2" name="JobNumber2" class="form-control"  value="2">
<input type="text" id="JobNumber3" name="JobNumber3" class="form-control"  value="3">
and you want validate the value on blur ,just try as below:
$("[id^='JobNumber']").blur(function(e)        
{             
var jobnumber=$(this).val();             
$.ajax({               
 type: "POST",               
 url: "#Url.Action("VerifyJobNumber")",               
 data: { "jobNumber": jobnumber },               
 dataType: "text",                
success: function (respdata) {                     
alert(respdata);                                  
 }            
});        
});
With a controller :
[HttpPost]       
public IActionResult VerifyJobNumber(string jobNumber)        
{            
return Ok(jobNumber);        
}
The result:

Change color of multiple elements using a color picker

I am trying to figure out how I can change the color of all elements with the class of "change" using the Really Simple Bootstrap Color Picker. Here is the JS code.
$(function() {
var $inputs = $('input.color');
$inputs.colorPicker({pickerDefault: '#99CCFF'});
$inputs.each(function(idx, item) {
var $input = $(item);
var $target = $input.parents('.control-group:first').find('label:first');
$target.css('color', $input.val());
$input.on('colorPicker:preview colorPicker:change', function(e, value) {
$target.css('color', value);
});
$input.on('colorPicker:addSwatch', function(e, value) {
//do something with custom color (e.g. persist on the server-side)
});
});
});
HTML (I dont plan on using a form for all my elements. None of the elements below have a class of "change". I just included the code I had originally.)
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">Pick a Color</label>
<div class="controls input-prepend">
<input type="text" name="color" class="color addSwatch" />
</div>
</div>
<div class="control-group">
<label class="control-label">Pick a Color</label>
<div class="controls input-prepend">
<input type="text" name="color" class="color input-small" />
</div>
</div>
</form>

Combining filters Jquery Isotope

I am trying to combine filters like this example: http://codepen.io/desandro/pen/JEojz/?editors=101 but I can't.
Codepen: http://codepen.io/anon/pen/gpbypp
HTML:
<div id="filters">
<label class="pull-left">Seats: </label>
<div class="btn-group" data-toggle="buttons" data-filter-group="seats">
<label class="btn btn-default active">
<input type="radio" data-filter="*">All
</label>
<label class="btn btn-default">
<input type="radio" data-filter=".seats-4">4
</label>
<label class="btn btn-default">
<input type="radio" data-filter=".seats-5">5
</label>
</div>
<br>
<br>
<label class="pull-left">Transmission: </label>
<div class="btn-group" data-toggle="buttons" data-filter-group="transmission">
<label class="btn btn-default">
<input type="radio" data-filter=".manual">Manual
</label>
<label class="btn btn-default">
<input type="radio" data-filter=".auto">Auto
</label>
</div>
</div>
<div id="isotope-container">
<div class="col-sm-3 item seats-5 auto">
<h3>Volkswagen Polo</h3>
<p>5 seats auto</p>
</div>
<div class="col-sm-3 item seats-4 auto">
<h3>Suzuki Jimny</h3>
<p>4 seats auto</p>
</div>
<div class="col-sm-3 item seats-4 manual">
<h3>Volkswagen Caddy</h3>
<p>4 seats manual</p>
</div>
<div class="col-sm-3 item seats-5 manual">
<h3>Opel Zafira</h3>
<p>5 seats manual</p>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.isotope/2.2.0/isotope.pkgd.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
Isotope website
Starting with the codepen from Pixelsmith, I swapped out the JS to match the example that you posted. I believe this is what you were looking for. The is-checked formatting is a little wonky, but the functionality is there.
This is the new JS. Working example
$('.filters').on( 'click', '.btn', function() {
var $this = $(this);
var $buttonGroup = $this.parents('.btn-group');
var filterGroup = $buttonGroup.attr('data-filter-group');
// set filter for group
filters[ filterGroup ] = $this.attr('data-filter');
var filterValue = concatValues( filters );
$container.isotope({ filter: filterValue });
});
// change is-checked class on buttons
$('.btn-group').each( function( i, buttonGroup ) {
var $buttonGroup = $( buttonGroup );
$buttonGroup.on( 'click', 'button', function() {
$buttonGroup.find('.is-checked').removeClass('is-checked');
$( this ).addClass('is-checked');
});
});
// flatten object by concatting values
function concatValues( obj ) {
var value = '';
for ( var prop in obj ) {
value += obj[ prop ];
}
return value;
}
There's an error in your JS that's stopping the function from running, and then quirks in your code that seem to break the function.
In the function you need to change the line $('#filters input').on('click', '.btn', function() { to $('#filters').on('click', '.btn', function() {, otherwise you're asking the browser to watch all inputs in the #filters div for a click on .btn and that won't ever happen.
Second, I couldn't get the filters to work with the label/radio combination that you'd created, but I did get it to work with buttons. Is there a reason you're choosing radio buttons? If so you'll need to work with the code a bit more to make it happen, and you may want to move away from click and use attr('checked') instead.
Working Codepen
Good luck!

How To pass the modified data from dual listbox to controller?

I have created a form with two listboxes in which it is possible to move the items from one listbox into another.
The view also loads correctly, but I haven't figured out how to send the modified listbox data back to controller.
The view code is the following:
<script>
$(function() {
$(document)
.on("click", "#MoveRight", function() {
$("#SelectLeft :selected").remove().appendTo("#SelectRight");
})
.on("click","#MoveLeft", function() {
$("#SelectRight :selected").remove().appendTo("#SelectLeft");
});
});
#Html.Hidden("RedirectTo", Url.Action("UserManagement", "Admin"));
<h2>User</h2>
<div class="container">
<form role="form">
<div class="container">
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label for="SelectLeft">User Access:</label>
<select class="form-control" id="SelectLeft" multiple="multiple" data-bind="options : ownership, selectedOptions:ownership, optionsText:'FirstName'">
</select>
</div>
</div>
<div class="col-md-2">
<div class="btn-group-vertical">
<input class="btn btn-primary" id="MoveLeft" type="button" value=" << " />
<input class="btn btn-primary" id="MoveRight" type="button" value=" >> " />
</div>
</div>
<div class="col-md-5">
<div class="form-group">
<label for="SelectRight">Owners:</label>
<select class="form-control" multiple="multiple" id="SelectRight" multiple="multiple" data-bind="options : availableOwners, selectedOptions:availableOwners, optionsText:'FirstName'">
</select>
</div>
</div>
</div>
</div>
</form>
</div>
<script>
var data=#(Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model)));
var selectedOwners = #Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(ViewBag.AccessOwners));
var availableOwners = #Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(ViewBag.Owners));
function viewModel() {
this.username=ko.observable(data.Username);
this.password=ko.observable(data.Password);
this.email=ko.observable(data.Email);
this.isActive=ko.observable(data.IsActive);
this.userId = ko.observable(data.UserId);
this.ownership=ko.observableArray(selectedOwners);
this.availableOwners = ko.observableArray(availableOwners);
this.submit = function()
{
$.ajax({
url: '#Url.Action("UserSave", "Admin")',
type: 'POST',
data: ko.toJSON(this),
contentType: 'application/json',
});
window.location.href = url;
return false;
}
this.cancel = function()
{
window.location.href = url;
return false;
}
};
ko.applyBindings(new viewModel());
var url = $("#RedirectTo").val();
I would be very thankful if anyone could suggest the way to pass all the selected options back to controller by populating the data with modified lists when the submit function is executed.
Thanks!
Before form submission save one side items values in an hidden input element. (comma separated values of listbox items.) The value of hidden element is sent to server by submitting the form. In controller you can do the next things.

angular JS view does not update properly

It's my first day of angular JS so I'm quite a newbie, I'm asking for some explanation about a behaviour that I don't understand. I'm trying to build a form that is composed of "panels". each panel contains a number of form elements (checkbox, radio group, etc.) or a message. At the beginning only the first panel is visible. Each time a user make an "action" (check something, choose something). A panel appears (sometimes more) based on the previous choice and other data.
Each form element has its model inside a controller. Each panel has a directive ngShow and a function in a controller that return whether or not this panel should be displayed. Here is a small extract from my code for two panels :
First Panel (ROOT panel always displayed)
<div class="panel panel-default" data-ng-show="true">
<div class="panel-heading">
<h3 class="panel-title">PLP</h3>
</div>
<div class="panel-body">
<div class="form-group">
<div class="col-xs-offset-0 col-xs-10">
<div class="radio">
<label><input type="radio" name="group" data-ng-model="choixPlp" value="plp1"> PLP 1</label>
</div>
<div class="radio">
<label><input type="radio" name="group" data-ng-model="choixPlp" value="plp2"> PLP 2</label>
</div>
<div class="radio">
<label><input type="radio" name="group" data-ng-model="choixPlp" value="plp3"> PLP3</label>
</div>
</div>
</div>
<p> {{choixPlp}} </p>
</div>
</div>
Second Panel
<div class="panel panel-default"
data-ng-show="fmotifRetourCommentaires()">
<div class="panel-heading">
<h3 class="panel-title">Dépot</h3>
</div>
<div class="panel-body">
<div class="form-group">
<div class="col-xs-offset-0 col-xs-10">
<label> Motif Retour : {{motif}} </label>
<p>Commentaires</p>
<div class="textarea"></div>
<textarea rows="" cols=""></textarea>
<br />
<button type="submit" class="btn btn-default">Déposer</button>
</div>
</div>
</div>
</div>
App.js
var app = angular.module('dynamicForm', []);
app.controller('MainCtrl', function($scope) {
$scope.choixPlp;
$scope.motif;
$scope.fmotifRetourCommentaires = function() {
if ($scope.choixPlp == "plp1") {
$scope.motif = "Degroupage Abusif";
return true;
}
if ($scope.choixPlp == "plp2") {
$scope.motif = "Deconstruction a tort";
return true;
}
return false;
};
});
My problem is that the view doesn't display the value of the variable "motif" in the second panel doesn't change when I change my choice on the first panel. It changes only if I click twice on the new choice. I did add this portion of code and it works but I don't understand why...
setInterval(function() {
$scope.$apply()
}, 500)
The reason it is not working on the first click but on the second click is because the model is not getting set till you the radio button loses focus (I think the sequence of watch and refresh is not proper for some reason)
Since you want to do some action based on the value of choixPlp, you can consider setting a watch on that variable. I have modified your code a little bit and used a variable show_panel_2, and that flag gets set or unset based on the function in watch.
<div class="panel panel-default"
data-ng-show="show_panel_2">
<div class="panel-heading">
<h3 class="panel-title">Dépot</h3>
</div>
<div class="panel-body">
<div class="form-group">
<div class="col-xs-offset-0 col-xs-10">
<label> Motif Retour : {{motif}} </label>
<p>Commentaires</p>
<div class="textarea"></div>
<textarea rows="" cols=""></textarea>
<br />
<button type="submit" class="btn btn-default">Déposer</button>
</div>
</div>
</div>
</div>
<script>
var app = angular.module('dynamicForm', []);
app.controller('MainCtrl', function($scope) {
$scope.choixPlp;
$scope.motif;
$scope.show_panel_2 = false;
$scope.$watch('choixPlp', function() {
if ($scope.choixPlp == "plp1") {
$scope.motif = "Degroupage Abusif";
$scope.show_panel_2 = true;
return;
}
if ($scope.choixPlp == "plp2") {
$scope.motif = "Deconstruction a tort";
$scope.show_panel_2 = true;
return;
}
$scope.show_panel_2 = false;
});
});
</script>

Categories