I'm trying to buid a moible Web Application with JQM and Knockout.
After my Knockout script is working i tried to get some style in it.
<form action="" data-bind=" template:{ 'if': loginVM, data: loginVM }">
//Some Code
</form>
<form action="" data-bind="template: { 'if': startVM, data: startVM }">
//Some Code
</form>
This is my more or less my Knockout script
var masterViewModel = {
loginVM: ko.observable(),
startVM: ko.observable(),
projektUnterbrechen: ko.observable(),
logout: ko.observable(),
projectStartVM: ko.observable()
};
var LoginVM = function () {
var self = this;
self.showDetails = function () {
if ((self.user() == "Gregor") && (self.password() == "gregrech")) {
masterViewModel.loginVM(null);
masterViewModel.startVM(new StartVM());
}
};
var StartVM = function () {
//Some Code
};
now after adding
<script src="../../Scripts/jquery.mobile-1.2.0.js" type="text/javascript"></script>
to my project my knockout doesn't work anymore.
It seems like my masterViewModel is not updatet!
For example i tried this:
var LoginVM = function () {
var self = this;
self.showDetails = function () {
if ((self.user() == "Gregor") && (self.password() == "gregrech")) {
masterViewModel.loginVM(null);
masterViewModel.startVM(new StartVM());
alert(masterViewModel.startVM()==null) //Messagebox displays "false"
}
};
var StartVM = function () {
alert(masterViewModel.startVM()==null) //Messagebox displays "true"
};
what could be a soloutin for this starnge problem?
JQM will alter the DOM once it is initialised, rendering many of the default ko bindings useless. Custom bindings can be written to work around these issues. It is hard to tell what the actual problem is here but that should give you something to read up on.
This is a useful article to start with: http://www.scottlogic.co.uk/blog/colin/2012/10/integrating-knockout-and-jquerymobile/
Related
I have c# classes that need to be passed into a knockout view model for look up purposes. Show a user status of 1000 or a role of 14038 is useless.
I need to be able to resolve those values to their text representation.
I've got some "reference data" populated in a database. On top of that are some T4 transformations which translate the reference data into C# code, for example:
public static class UserStatus
{
#region Members
public const string ClassName = "UserStatus";
public const int Pending = 1000;
public const int Active = 1001;
public const int Inactive = 1002;
public const int Deleted = 1003;
#endregion
}
This class is then used throughout the code to assign values to a User class, instead of saying user.UserStatus = 1000 it's user.UserStatus = UserStatus.Pending.
Now onto the issue...
CURRENT ISSUE
I've got a page that lists users in the system and one of the columns in the list is the user's status. Well that status passed from in the object is 1000 not "Pending". What I'd like to do is be able to resole 1000 to "Pending" in knockout. Problem is because knockout is executed on the client side it has no knowledge of my C# classes. Ideally I'd like to be able to pre-poplate a list in my controller with the possible values of a UserStatus, pass that into my knockout view model and have it loop through the possible statuses and resolve it based on that specific users status.
HTML Code
<tbody style="display: none;" class="table-body" data-bind="visible: true, foreach: { data: viewData.ClientGroups, as: 'ClientGroup' }">
<tr>
<td><span data-bind="html: ClientGroup.Name()"></span></td>
<td>TODO: Bind Client</td>
<td><span data-bind="html: ClientGroup.StatusText()"></span></td>
<td><span data-bind="html: ClientGroup.CreatedOnText()"></span></td>
</tr>
</tbody>
Creating and binding the knockout view model.
var viewData = {};
require(['main'], function () {
require(['message', 'viewModel/clientGroupViewModel', 'viewModel/clientGroupDetailsViewModel'],
function (message, clientGroupViewModel, clientGroupDetailsViewModel) {
$(document).ready(function () {
// enable ko punches
ko.punches.enableAll();
var json = #Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model, Newtonsoft.Json.Formatting.None, new IHtmlStringConverter()));
// I'd like to be able to create something here and pass it to my model.
var lookupValues = { blah }
viewData = new clientGroupViewModel(json, lookupValues);
var zdm = new clientGroupDetailsViewModel(json, lookupValues );
ko.applyBindings(viewData, document.getElementById('clientGroupAdmin'));
ko.applyBindings(zdm, document.getElementById('detailsModal'));
});
});
});
In the knockout view model
_self.StatusText = ko.computed(function () {
console.log('user status');
if (ko.utils.arrayFirst(UserStatus, function (value) {
console.log(value);
return value.Id == _self.UserStatus();
}));
return 'false';
});
Please read my extra info questions but this is one way how I'd go about it..
console.clear();
var $fakeAsync = function (api, result) {
var isEmptyCall = _.isNull(api) || api.length === 0;
dfd = $.Deferred(function () {
setTimeout(function () {
dfd.resolve(isEmptyCall ? null : result);
}, isEmptyCall ? 0 : 50);
});
return dfd.promise();
};
var $fakeData = [
{Id: 1, Name: 'foo', UserStatus: 1000},
{Id: 2, Name: 'bar', UserStatus: 1001},
{Id: 3, Name: 'stack', UserStatus: 1000},
{Id: 4, Name: 'overflow', UserStatus: 1002}
]
var $fakeUserStates = {
1000: 'pending',
1001: 'ready',
1002: 'cancelled',
1003: 'banned'
};
function MyViewModel() {
var self = this;
self.userStates = [];
self.loadMapping = function () {
return $fakeAsync('api/fakemappingcall', $fakeUserStates).done(function (result) {
console.log('mapping gathered');
self.userStates = result;
});
};
self.loadData = function () {
return $fakeAsync('api/fakedatacall', $fakeData);
};
self.method = function () {
self.loadData().done(function (result){
_.each(result, function (r) { _.extend(r, { 'UserStatusText': self.userStates[r['UserStatus']] }) });
console.log(result);
});
};
self.loadMapping().done(function () {
self.method();
});
};
ko.applyBindings(new MyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
I am using bootstrap dual listbox pluging in my ASP.NET MVC project.
I am also using Knockout in the project. I am trying to create bindingHandler for the this plugin to make it working smoothly with knockout.
here is what I tried
Binding Handler
ko.bindingHandlers.dualList = {
init: function (element, valueAccessor) {
$(element).bootstrapDualListbox({
selectorMinimalHeight: 160
});
$(element).on('change', function () {
$(element).bootstrapDualListbox('refresh', true);
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).bootstrapDualListbox('destroy');
});
},
update: function (element, valueAccessor) {
$(element).bootstrapDualListbox('refresh', true);
}
}
HTML
<select class="form-control" data-bind="foreach:{data: EditedElement().Operations, as : 'op'} , dualList: EditedElement().Operations" multiple>
<option data-bind="value: op.OperationID(), text: op.Name(), selected: op.IsSelected()"></option>
</select>
View Model
function SelectOperationVM(operationId, isSelected, name) {
var self = this;
self.OperationID = ko.observable(operationId);
self.IsSelected = ko.observable(isSelected);
self.Name = ko.observable(name);
self.copy = function () {
return new SelectOperationVM(self.OperationID(), self.IsSelected(), self.Name());
}
}
My problem is that I can not make sync between the viewModel observableArray, and the plugin.
In other words, I want the changes in the plugin (the user removed some options or added some options) to be reflected on the view model property, and vice verse
to sync, you need to pass multiple observables to custom binding
so your html should be like
<select class="form-control" data-bind="foreach: { data: Operations, as: 'op'}, dualList: { options: Operations, selected: Selected }" multiple>
<option data-bind="value: op.OperationID(), text: op.Name(), selected: op.IsSelected()"></option>
</select>
and custom binding be like
ko.bindingHandlers.dualList = {
init: function(element, valueAccessor) {
var data = ko.utils.unwrapObservable(valueAccessor());
$(element).bootstrapDualListbox({
selectorMinimalHeight: 160
});
$(element).on('change', function() {
// save selected to an observable
data.selected($(element).val());;
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).bootstrapDualListbox('destroy');
});
},
update: function(element, valueAccessor) {
// to view if there is an update (without this "update" will not fire)
var options = ko.utils.unwrapObservable(valueAccessor()).options();
$(element).bootstrapDualListbox('refresh', true);
}
}
also i have created a dirty working jsfiddle
I've been running through the react-router tutorial found here and I'm currently puzzled...
React-router doesn't recognise my component.
(I'm using React.js with Rails)
Here's the code:
var DefaultRoute = ReactRouter.DefaultRoute;
var Link = ReactRouter.Link;
var Route = ReactRouter.Route;
var RouteHandler = ReactRouter.RouteHandler;
var App = React.createClass({
getInitialState: function () {
return {
showTags: false,
current_user: this.props.current_user
};
},
_handleToggleTags: function() {
this.setState({
showTags: !this.state.showTags
})
},
render: function () {
return <div>
<Header
onToggleTags={ this._handleToggleTags }
user={this.props.current_user}
/>
<RouteHandler/>
<div id="images">
<ImageBox/>
</div>
</div>;
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="tags" handler={TagsBox}/>
</Route>
);
ReactRouter.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
If I move TagsBox before App it works, though nobody else seems to be doing this. What am I missing?
If it makes a different, the current structure of my components is:
app.js.jsx
Tags
_tags_box.js.jsx
In your position it seems best to run the router once all scripts are loaded.
Try wrapping the run method in this code:
document.addEventListener("DOMContentLoaded", function() {
ReactRouter.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
}
I Have this Fiddle
It is working perfectly in the fiddle, but as soon as i put everything in a ASP.NET MVC4 Application the second ViewModel is not loaded!
This normally is only when i add the following Script Tag to my Project
<script src="../../Scripts/jquery.mobile-1.2.0.js" type="text/javascript"></script>
Do i need some JQuery Functions to load the second ViewModel or what did i wrong? ;)
Here is my code:
<form action="" data-bind=" template:{ 'if': loginVM, data: loginVM }">
//SomeCode
</form>
<form action="" data-bind=" template:{ 'if': startVM, data: startVM}">
//Some Code
</form>
<script type="text/javascript">
var masterViewModel = {
loginVM: ko.observable(),
startVM: ko.observable(),
projektUnterbrechen: ko.observable(),
logout : ko.observable(),
projectStartVM: ko.observable()
};
var LoginVM = function () {
var self = this;
self.mandant = ko.observable();
self.user = ko.observable();
self.password = ko.observable();
self.showDetails = function () {
if ((self.user() == "Gregor") && (self.password() == "gregrech")) {
masterViewModel.loginVM(null);
masterViewModel.startVM(new StartVM());
**//alert(masterViewModel.startVM()!=null) //==True**
}
else {
alert("Username oder Passwort falsch");
}
};
};
var StartVM = function () {
self = this;
**//alert(masterViewModel.startVM()!=null) //==False**
//Um weiterzumachen muss man eingeloggt sein
self.favoriten = ko.observableArray([
{
projectName: "Favorit1"
},
{
projectName: "Favorit2"
},
{
projectName: "Favorit3"
}
]);
//Die zuletzt verwendeten Projekte
self.zuletzt = ko.observableArray([
{
lastProjName: "Zuletzt1"
},
{
lastProjName: "Zuletzt2"
},
{
lastProjName: "Zuletzt3"
}
]);
self.showStart = function (projectName, data, event) {
masterViewModel.projectStartVM(new ProjectStartVM(projectName));
masterViewModel.startVM(null);
};
};
masterViewModel.loginVM(new LoginVM());
$(document).on('pageinit', function () {
ko.applyBindings(masterViewModel);
});
</script>
Thank you for your answers
EDIT:
I found out, that i get different Values if i compare my masterViewModel.startVM()!= null even if it has to be the same!
Maybe this helps you!
Please look at the comments with "**" in my Code to understand what i mean
Try to load viewmodel in ready event not pageinit event.
$(document).ready(function () {
ko.applyBindings(masterViewModel);
});
In a single asp.net MVC page, I've to show 3 reports of same layout/structor. So I created a page that generated a report using Knockout databind and 10 reports are generated by passing some additional parameters.
I tried to load all 3 reports using Ajax partial view and by this way I'm populating those 10 reports in one single page.
Now I happen to see a strange issue, the last report data appears for all the 3 reports though the data for each report is different. Anyone knows how to handle this issue ?
Main Page View, where I call 3 reports via Ajax call.
<div id="RecentlyAddedReport" ></div>
<div id="RecentlyConsultedReport"></div>
<div id="RecentlyPrescribedReport"></div>
<!-- start here -->
<script type="text/javascript">
$(document).ready(function () {
AjaxLoadReport('RecentlyAddedReport', 'Patient/Report?type=RecentlyAdded&name=Recently Added');
AjaxLoadReport('RecentlyConsultedReport', 'Patient/Report?type=RecentlyConsulted&name=Recently Consulted');
AjaxLoadReport('RecentlyPrescribedReport', 'Patient/Report?type=RecentlyPrescribed&name=Recently Prescribed');
});
function AjaxLoadReport(reportType, actionURL) {
var resultDiv = $('#' + reportType);
$.ajax({ type: "GET", url: actionURL, data: {},
success: function (response) {
resultDiv.html('');
resultDiv.html(response);
}
});
}
</script>
Report View - I call this same view for all the 3 reports
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<TryHomeo.Web.UI.Models.UserControlViewData>" %>
<script type="text/javascript" src="../../scripts/jquery-1.7.1.min.js"></script>
<script src="../../Scripts/jquery.timeago.js" type="text/javascript"> </script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"> </script>
<h1>
<%:Model.ReportName%>
</h1>
<div class='loadingIndicator' title="Loading..."></div>
<ul id="<%=Model.ReportType %>" data-bind="template: { name: 'patient-template', foreach: patients}"></ul>
<script type="text/html" id="patient-template">
<span>
<li>
<div>
<a data-bind="text: PatientName"></a>- (<abbr class='timeago' data-bind="timeago: DatedString"></abbr>)
</div>
</li>
</span>
</script>
<script type="text/javascript">
// ReportViewModel View Model //
function ReportViewModel() {
var self = this;
self.patients = ko.observableArray([]);
}
var objVM = new ReportViewModel();
ko.applyBindings(objVM);
// Using jQuery for Ajax loading indicator
$(".loadingIndicator").ajaxStart(function () { $(this).fadeIn(); }).ajaxComplete(function () { $(this).fadeOut(); });
$(document).ready(function () {
$.ajax({
dataType: 'json',
url: '/Patient/GetPatientReport/?type=' + '<%=Model.ReportType %>' + '&' + '<%=DateTime.Now.ToString() %>',
success: SetData,
error: function (xhr, ajaxOptions, thrownError) { alert(xhr.status); alert(thrownError); }
});
});
function SetData(data) {
objVM.patients(data);
}
ko.bindingHandlers.timeago = {
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var $this = $(element);
// Set the title attribute to the new value = timestamp
$this.attr('title', value);
// If timeago has already been applied to this node, don't reapply it -
// since timeago isn't really flexible (it doesn't provide a public
// remove() or refresh() method) we need to do everything by ourselves.
if ($this.data('timeago')) {
var datetime = $.timeago.datetime($this);
var distance = (new Date().getTime() - datetime.getTime());
var inWords = $.timeago.inWords(distance);
// Update cache and displayed text..
$this.data('timeago', { 'datetime': datetime });
$this.text(inWords);
} else {
// timeago hasn't been applied to this node -> we do that now!
$this.timeago();
}
}
};
</script>