Knockout Component View Not Updating When Its ViewModel Observable Changes - javascript

I have a component setup to use AMD to get the html template and viewmodel code. Everything works fine. The component loads when it is supposed to and behaves fine with the params passed to it. The problem is I defined an observable in the viewModel whose value shows up in the template view, but when the observable's value changes the text on the view does NOT change. Can anyone explain what is going on here? The text I am trying to bind to is modalTitle. When the modal loads its title is 'TEMP' but if I go to the console and type 'window.modalTitle()' I get 'CREATE REPORT SCHEDULE'. It's like the view is getting the first value of the observable and then ignoring it after that. Is there anyway I can force it to look for updates?
ViewModel: (schedules.component.js)
define(['knockout'], function (ko) {
console.log('schedules hit');
loadCss('schedules');
function SchedulesViewModel(params) {
this.scheduledItems = params.scheduledItems;
this.itemName = params.itemName;
this.modalTitle = ko.observable("TEMP");
window.modalTitle = this.modalTitle;
}
SchedulesViewModel.prototype.initiateAddScheduledItem = function () {
this.modalTitle("CREATE " + this.itemName + " SCHEDULE");
$('#schedulesModal').modal('show');
};
SchedulesViewModel.prototype.removeSelectedScheduledItem = function () {
this.chosenValue('dislike');
};
window.ReportsApp.SchedulesViewModel = SchedulesViewModel;
return SchedulesViewModel;
});
View Template
<div id="schedulesModal" class="modal fade lcmsModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<!--<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>-->
<img src="/Content/images/modalASLogo.png" style="float: right;" />
<h4 class="modal-title" data-bind="text: modalTitle()">Test Title</h4>
</div>
<div class="modal-body">
<p>One fine body ...</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">CANCEL</button>
<button type="button" class="btn btn-primary">SAVE</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- /Bootstrap Modal -->

It does not get changed because this.itemName has not been defined as an observable. it is better to define a computed observable which will automatically update whenever any observables change.
Instead of using prototype to add methods, you can use knockout function which foes it for you.
Example :https://jsfiddle.net/kyr6w2x3/34/
function SchedulesViewModel(params) {
var self = this ;
self.scheduledItems = ko.observable(params.scheduledItems);
self.itemName = ko.observable(params.itemName);
self.modalTitle = ko.observable("TEMP");
self.chosenValue= ko.observable();
self.modalTitle = ko.computed(function() {
return "CREATE " + self.itemName() + " SCHEDULE" ;
}, self);
// you can change below to show your modal whenever you want
$('#schedulesModal').modal('show');
self.removeSelectedScheduledItem = function (){
self.chosenValue('dislike');
}
}
ko.applyBindings(new SchedulesViewModel({scheduledItems:"scheduledItems" ,itemName : "itemName" }));
Update : yes you can have multiple view models or better to say nested view models. Look at the new example and see how you can communicate between your models.https://jsfiddle.net/kyr6w2x3/35/

Related

Parent to Child communication not working as expected

I have two components, a parent and a child.
Every time I click on a certain button in the parent component, an object gets populated and sent to the child component via an #Input decorator.
The issue here is that even though the changes to the object are detected in the child component, the data that I'm trying to populate will only appear on even clicks.
This is what my code currently looks like:
parent.component.ts
private toSend = {};
public sendToChild() {
var objectToSend = {
headerMessage:`Title`,
bodyMessage:"Body"
};
this.toSend = { ...objectToSend };
$('#childComponent').appendTo("body").modal('toggle');
}
parent.component.html
<child-component #childComponent [data]="toSend">
</child-component>
child.component.ts
public headerMessage: string = "";
public bodyMessage: string = "";
#Input('data')
set data(data: any) {
if (data !== undefined && data.length !== 0) {
this.setData(data);
}
}
private setData(el): void {
for (let key in el) {
switch (key) {
case "headerMessage":
this.headerMessage = el[key];
break;
case "bodyMessage":
this.bodyMessage = el[key];
break;
}
}
}
child.component.html
<div class="modal" tabindex="-1" role="dialog" id="childComponent">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header" style="padding: 1rem;">
<h5 class="modal-title">{{headerMessage}}</h5>
<button type="button" class="close" id="btn-close-id" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" id="modal-body-id">
<div class="container-fluid">
<div class="col-12">
{{bodyMessage}}
</div>
</div>
</div>
<div class="modal-footer" style="padding: 1rem;">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
I also tried with ngOnChanges and I was able to print the object I was sending every time but I had the same problem.
You could force the detection by using the ChangeDetectorRef
import { ..., ChangeDetectorRef } from '#angular/core';
Add it on your constructor
constructor(private _cdr: ChangeDetectorRef) {...}
And in your child component
this._cdr.markForCheck();
// or if not working
this._cdr.detectChanges()
Another thing, JQuery is not recommanded with Angular. If you want to use modal in your project, you should check Angular material modal or make your own modal component.

jquery bootstrap 4 typescript. using jquery to close modal not working

I am attempting to use jquery to close a bootstrap modal on an angular project in typescript code.
The code:
function call in html:
(click)="populaterfpfromsaved(i, createSaved, createProp )"
createSaved and createProp are local references on modals
here they are on the modals:
<ng-template #createProp let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Request For Proposal</h4>
<button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
....
<ng-template class="mw-100 w-75" #createSaved let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Saved RFPs</h4>
<button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body table-responsive">
<table class="table table-hover">
<thead>
....
and the code in my typescript
populaterfpfromsaved(index, create, prop){
console.log('it fired to open the rfp modal');
const scoperfp = this.savedrfps[index];
this.savedevent.name = scoperfp.eventname;
this.savedevent.date = scoperfp.datename;
this.savedevent.programdate = scoperfp.datevalue;
this.savedevent.dateflex = scoperfp.dateflex;
this.savedevent.eventpurpose = scoperfp.eventpurpose;
this.savedevent.starttime = scoperfp.startime;
this.savedevent.starttimeflex = scoperfp.starttimeflex;
this.savedevent.endtime = scoperfp.endtime;
this.savedevent.endtimeflex = scoperfp.endtimeflex;
this.savedevent.headcount = scoperfp.headcount;
this.savedevent.eventdetails = scoperfp.eventdetails;
(<any>jQuery(create)).modal('hide');
(<any>jQuery(prop)).modal('show');
}
but the modals do not change.
Is there something I haven't set up right
Change like this it may be work for you let try this once.
change your html file,
<ng-template #createProp let-c="close" let-d="dismiss">
to <div class="modal" id='createProp'></div>
typescript file,
$('#createProp').modal('toggle');
use this above line for wherever you want to close your modal.
Make sure you have included jquery plugin in your app and declared variable
declare var $: any;
angular.cli.json
"scripts": [ "../node_modules/jquery/dist/jquery.min.js" ]
For more reference,
Close Bootstrap Modal
https://medium.com/#swarnakishore/how-to-include-and-use-jquery-in-angular-cli-project-592e0fe63176

Confirm user from a remote api url in AngularJS?

so, I have this bug in my code, that I can't quite figure it out. So, my app needs to do is to click on the confirm button to remove that user from its list within the remote api url. So, when I click on confirm button, it removes the user from console.log but it does not update the view. So, please check out my code and I will be thankful for your help.
if you are visiting my plunker, please write comments here, so I can know where was the bug fixed. Thanks for your time.
Here is a full plunker: https://plnkr.co/edit/nWFi81KannLcQfratr0t
PS: in the plunker, there is a UI-Bootstrap that it need it to work with it, but plunker did not run with it so, I have comment UI-Bootstrap.
Here is some code
$scope.confirmedAction = function(person) {
var index = $scope.userInfo.lawyers.map(function(e) {
return e.id;
}).indexOf(person.id);
$scope.userInfo.lawyers.splice(index, 1);
console.log($scope.userInfo.lawyers);
// console.log($scope.userInfo);
$window.location.href = '#/lawyer';
HomeController
var isConfirmed = false;
app.controller('HomeController', function($scope, people) {
if (!isConfirmed) {
people.getUserInfo().then(function (response) {
$scope.userInfo = response.data;
//console.log($scope.userInfo);
}, function (error) {
console.log(error)
});
}
});
The user isn't removed because you are removing it form client side but you didn't update the sever with the changes so after the delete the the page is reloading the data again from the server which will be the full array.
You should send this removed user back to server
Notice: the UI-Bootstrap you removed prevent the modal from injecting, but i can see the value throw the console.log
This is a full sample acutlly i used bootstrap framework in the view to handle the confirm dialog.
When user click on a item we should select it as target in this sample
our target detect by $scope.selectUser() function, after that and
when delete is confirmed we use splice the target from our array
by detect the index of the target
var app = angular.module("app", []);
app.controller("ctrl", ["$scope", function($scope) {
$scope.users = [{
name: "John"
},
{
name: "Mike"
}
];
$scope.selectUser = function(user) {
$scope.userIs = user;
}
$scope.deleteConfirmed = function() {
$scope.users.splice($scope.users.indexOf($scope.userIs), 1);
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<div ng-app="app" ng-controller="ctrl">
<br />
<div class="col-lg-4 col-lg-offset-4">
<ul class="list-group">
<li class="list-group-item" ng-repeat="user in users">
{{user.name}}
<a data-toggle="modal" data-target="#myModal" class="text-danger pull-right" ng-click="selectUser(user)">Delete</a>
</li>
</ul>
</div>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Delete...</h4>
</div>
<div class="modal-body">
Are you sure want delete user "{{userIs.name}}"?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" ng-click="deleteConfirmed()" data-dismiss="modal">do it</button>
</div>
</div>
</div>
</div>
</div>

Angularjs: Able to log data but cannot display

I am attempting to create a form that upon clicking submit calls a function that will post to a php page (which runs a query), then displays those results on the page.
If I call said function in my controller on load, I get my expected result(data presented in html table in a modal). However if I call that function upon clicking submit. I can log the data result, but it does not display on my page.
$scope.report = {};
var url = "";
// calling our submit function.
$scope.submitForm = function() {
$http.post('url.php').success(function(data) {
// Stored the returned data into scope
$scope.names = data;
console.log(data);
$('#myModal').modal();
});
};
<button type = "button" class="btn btn-success" ng-click="submitForm()" >Submit Request</button>
<div class="modal fade" id="myModal" role="dialog" >
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content" >
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Modal Header</h4>
</div>
<div class="modal-body">
<table class="table table-striped table-bordered">
<tr>
<th>Name</th>
</tr>
<tr ng-repeat="name in names | filter:search_query">
<td><span>{{name.first}}</span></td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Try adding a small $timeout before opening your modal (don't forget to inject $timeout to your controller):
$scope.submitForm = function() {
$http.post('url.php').success(function(data) {
// Stored the returned data into scope
$scope.names = data;
console.log(data);
$timeout(function () {
$('#myModal').modal();
},250);
});
};

How to get the id and name of an object on button click and display them in a modal in asp.net view

I have a strongly typed view in which I am looping over some objects from a database and dispaying them in a jumbobox with two buttons in it. When I click one of the buttons I have a modal popping up. I'd like to have somewhere in this modal the name and the id of the corresponding object, but I do not really know how to do this. I am a bit confused where to use c# and where javascript. I am a novice in this, obviously.
Can someone help?
This is the code I have so far. I don't have anything in relation to my question, except the code for the modal :
#model IEnumerable<eksp.Models.WorkRole>
#{
ViewBag.Title = "DisplayListOfRolesUser";
}
<div class="alert alert-warning alert-dismissable">You have exceeded the number of roles you can be focused on. You can 'de-focus' a role on this link.</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var dataJSON;
$(".alert").hide();
//make the script run cuntinuosuly
$.ajax({
type: "POST",
url: '#Url.Action("checkNumRoles", "WorkRoles")',
dataType: "json",
success: successFunc,
error: errorFunc
});
function successFunc(data, status) {
if (data == false) {
$(".alert").show();
$('.btn').addClass('disabled');
//$(".btn").prop('disabled', true);
}
}
function errorFunc() {
alert('error');
}
});
</script>
#foreach (var item in Model)
{
<div class="jumbotron">
<h1>#Html.DisplayFor(modelItem => item.RoleName)</h1>
<p class="lead">#Html.DisplayFor(modelItem => item.RoleDescription)</p>
<p> #Html.ActionLink("Focus on this one!", "addWorkRoleUser", new { id = item.WorkRoleId }, new { #class = "btn btn-primary btn-lg" })</p>
<p> <button type="button" class="btn btn-default btn-lg" data-toggle="modal" data-target="#myModal">Had role in the past</button> </p>
</div>
}
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">#Html.DisplayFor(modelItem => item.RoleName)//doesn't work</h4>
</div>
<div class="modal-body">
<p>Some text in the modal.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Save</button>
</div>
</div>
</div>
</div>
I think your confusing the server side rendering of Razor and the client side rendering of the Modal. The modal cannot access your Model properties as these are rendered server side before providing the page to the user. This is why in your code <h4 class="modal-title">#Html.DisplayFor(modelItem => item.RoleName)//doesn't work</h4> this does not work.
What you want to do is capture the event client side in the browser. Bootstrap allows you to achieve this by allowing you to hook into events of the Modal. What you want to do is hook into the "show" event and in that event capture the data you want from your page and supply that to the Modal. In the "show" event, you have access to the relatedTarget - which is the button that called the modal.
I would go one step further and make things easier by adding what data you need to the button itself as data-xxxx attributes or to DOM elements that can be easily access via JQuery. I have created a sample for you based on what you have shown to give you an idea of how it can be achieved.
Bootply Sample
And if needed... How to specify data attributes in razor
First of all
you will need to remove the data-toggle="modal" and data-target="#myModal" from the button, as we will call it manually from JS and add a class to reference this button later, your final button will be this:
<button type="button" class="btn btn-default btn-lg modal-opener">Had role in the past</button>
Then
In your jumbotron loop, we need to catch the values you want to show later on your modal, we don't want to show it, so we go with hidden inputs:
<input type="hidden" name="ID_OF_MODEL" value="#item.WorkRoleId" />
<input type="hidden" name="NAME_OF_MODEL" value="#item.RoleName" />
For each information you want to show, you create an input referencing the current loop values.
Now you finally show the modal
Your document.ready function will have this new function:
$('.modal-opener').on('click', function(){
var parent = $(this).closest('.jumbotron');
var name = parent.find('input[name="NAME_OF_MODEL"]').val();
var id = parent.find('input[name="ID_OF_MODEL"]').val();
var titleLocation = $('#myModal').find('.modal-title');
titleLocation.text(name);
// for each information you'll have to do like above...
$('#myModal').modal('show');
});
It simply grab those values we placed in hidden inputs.
Your final code
#model IEnumerable<eksp.Models.WorkRole>
#{
ViewBag.Title = "DisplayListOfRolesUser";
}
<div class="alert alert-warning alert-dismissable">You have exceeded the number of roles you can be focused on. You can 'de-focus' a role on this link.</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var dataJSON;
$(".alert").hide();
//make the script run cuntinuosuly
$.ajax({
type: "POST",
url: '#Url.Action("checkNumRoles", "WorkRoles")',
dataType: "json",
success: successFunc,
error: errorFunc
});
function successFunc(data, status) {
if (data == false) {
$(".alert").show();
$('.btn').addClass('disabled');
//$(".btn").prop('disabled', true);
}
}
function errorFunc() {
alert('error');
}
$('.modal-opener').on('click', function(){
var parent = $(this).closest('.jumbotron');
var name = parent.find('input[name="NAME_OF_MODEL"]').val();
var id = parent.find('input[name="ID_OF_MODEL"]').val();
var titleLocation = $('#myModal').find('.modal-title');
titleLocation.text(name);
// for each information you'll have to do like above...
$('#myModal').modal('show');
});
});
</script>
#foreach (var item in Model)
{
<div class="jumbotron">
<input type="hidden" name="ID_OF_MODEL" value="#item.WorkRoleId" />
<input type="hidden" name="NAME_OF_MODEL" value="#item.RoleName" />
<h1>#Html.DisplayFor(modelItem => item.RoleName)</h1>
<p class="lead">#Html.DisplayFor(modelItem => item.RoleDescription)</p>
<p> #Html.ActionLink("Focus on this one!", "addWorkRoleUser", new { id = item.WorkRoleId }, new { #class = "btn btn-primary btn-lg" })</p>
<p> <button type="button" class="btn btn-default btn-lg" data-toggle="modal" data-target="#myModal">Had role in the past</button> </p>
</div>
}
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">#Html.DisplayFor(modelItem => item.RoleName)//doesn't work</h4>
</div>
<div class="modal-body">
<p>Some text in the modal.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Save</button>
</div>
</div>
</div>

Categories