knockoutjs automatic generation of viewmodel - javascript

Does anyone know if it is possible to generate the viewmodel for knockoutjs automatically.
1) I have (for example) the following html code:
<input type="text" data-bind="value: XYZ" />
<input type="text" data-bind="value: ABC" />
<input type="text" data-bind="value: Prop1" />
2) Then I need to create a viewmodel like this:
function ViewModel()
{
this.XYZ = ko.observable();
this.ABC = ko.observable();
this.Prop1 = ko.observable();
}
I would like to skip step 2, because I already defined the properties which should be available in the html-markup (sort of).
update:
As mentioned in the comments, there would be drawbacks to do such a thing. (That's probably the reason it is not supported by knockoutjs). I will take a different approach, but nevertheless I made a simple Javascript function which can generate a ViewModel out of a VERY simple markup. If anyone is interested, that's what I came up with:
function getViewModelFromView(selector) {
var vm = {};
$("[data-bind]", selector).each(function () {
var attribute = $(this).attr("data-bind");
if (attribute.indexOf(':') > 0) {
vm[attribute.slice(attribute.indexOf(':')+1).trim()] = ko.observable();
}
});
return vm;
}

Related

KnockoutJS : Validate model's property only if the bound control is visible

I have model in a page that is bound to several controls. Based on some condition some of these controls will be visible or invisible. And on the final submit I should only validate those which are visible.
The following is a sample code to explain my requirement
<script src="knockout-3.4.0.js" type="text/javascript"></script>
<input type="checkbox" data-bind="checked:requireAge" >Age Required</input><br />
Name : <input data-bind="value:Name" /><br />
<div data-bind="visible:requireAge">
Age: <input data-bind="value:Age,visible:requireAge" />
</div>
<button type="button" onclick="validateModel();">Validate</button>
<script type="text/javascript">
var viewModel = { Name: ko.observable(), Age: ko.observable(),requireAge:ko.observable(false) };
ko.applyBindings(viewModel);
function validateModel() {
//validate visible properties and throw a common message that all visible fields should be filled
}
</script>
My suggestion is to use the knockout-validation library (you made no mention of it in your question so I assume you're not using it already) It ties in seamlessly with knockout and makes validation far more convenient. I've used it extensively over the past year and its make my life a whole lot easier. No need to create computeds to keep track of whether an observable contains a valid value or not. You can find the knockout-validation library on github.
In your case you can simply do the following:
var viewModel = function(){
var self = this;
self.name = ko.observable();
self.requireAge = ko.observable(false);
self.age = ko.observable().extend({
required: {
onlyIf: function() { return self.requireAge(); }
}
});
};
Validation error messages are inserted automatically below the element the observable is bound to. You can also create your own validation rules but there are many that work straight out the box including the one demonstrated above. You can even use some data attributes for some rules. This is probably the best way to go about validation in conjunction with knockout.
Based on some condition some of these controls will be visible or invisible.
It would be better if these conditions are contained in the model. And validation method too.
See snippet:
var viewModel = function() {
this.Name = ko.observable("");
this.Age = ko.observable("");
this.requireAge = ko.observable(false);
this.isValid = ko.computed(function() {
if (ko.unwrap(this.Name).length === 0) return false;
if (ko.unwrap(this.requireAge) &&
ko.unwrap(this.Age).length === 0) return false;
return true;
}, this);
};
window.onload = function() {
ko.applyBindings(new viewModel());
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="checkbox" data-bind="checked:requireAge" >Age Required</input><br />
Name : <input data-bind="value:Name" /><br />
<div data-bind="visible:requireAge">
Age: <input data-bind="value:Age,visible:requireAge" />
</div>
<div>is valid: <span data-bind="text: isValid"></span></div>

Referencing properties of object constructor outside view model in knockout

So, I'm not entirely sure how to phrase this question as it's sort of two in one. I'm having a weird issue where I have an object constructor to create new 'projects' from an HTML form which are then pushed into an observableArray when the form is submitted. Everything works fine but to reference the related observables I have to use 'value: Project.title' or 'value: Project.whatever'. I haven't seen 'value: NameOfConstructor.property' used in any of the examples I've seen. I assume this works this way because the constructor is outside of my view model.
My question is this: Is there a better way to assign the value of a property in a constructor that is not in my view model? In other words, is there a better or more correct way than 'Project.title', ect?
I ask partially because one thing in my code doesn't work currently; the knockout enable property doesn't work on my "New Project" button, it stays disabled even if there is something written in the 'title' input box. I have the feeling it's because it's written as data-bind='enable: Project.title' but I can't figure how else to write it.
I've included a jsfiddle for reference though it obviously isn't working because of external dependencies.
https://jsfiddle.net/bmLh0vf1/1/
My HTML:
<form id='addBox' action='#' method='post'>
<label for='pTitle'> Title: </label>
<input id='pTitle' data-bind='value: Project.title' />
<br/>
<label for='pPriority'> Priority </label>
<select id='pPriority' data-bind='options: priorityOptions, value: Project.priority'></select>
<br/>
<button data-bind='enable: Project.title, click: newProject'>New Project</button>
</form>
And my Javascript:
function Project(title, priority) {
this.title = ko.observable(title);
this.priority = ko.observable(priority);
};
function ProjectViewModel() {
var self = this;
this.priorityOptions = ko.observableArray(['High', 'Medium', 'Low'])
this.projectList = ko.observableArray([
new Project('Create App', 'High')
]);
this.newProject = function() {
var np = new Project(Project.title, Project.priority);
self.projectList.push(new Project(Project.title, Project.priority));
console.log(self.projectList().length);
if (self.projectList().length > 1) {
console.log(self.projectList()[1].title());
};
}
};
var viewModel = new ProjectViewModel();
$(document).ready(function() {
ko.applyBindings(viewModel);
});
Lastly, I apologize if I've missed any posting conventions or if my code is especially bad. I'm very new and still teaching myself.
Your code is setting title and priority properties on the object created by new Project, but then later you're expecting to see those properties on Project itself. It doesn't have them; Project is the function, not the object created by new Project. So Project.title and Project.priority will give you undefined (and not an observable, and so not useful targets for the value binding).
Instead, have an "editing" Project instance that you use, binding the value of the inputs to the editing' instances title and priority, and then in newProject grab that instance and replace it with a new, fresh one.
Roughly speaking, in ProjectViewModel's constructor:
this.editing = ko.observable(new Project());
Update Project to default title and priority:
function Project(title, priority) {
this.title = ko.observable(title || "");
this.priority = ko.observable(priority || "Medium");
}
And in the bindings:
<input id='pTitle' data-bind='value: editing().title' />
<select id='pPriority' data-bind='options: priorityOptions, value: editing().priority'></select>
And in newProject:
var np = this.editing();
this.editing(new Project());
Then use np (instead of another new Project) when adding to the array.
Here's a simplified example:
function Project(title, priority) {
this.title = ko.observable(title || "");
this.priority = ko.observable(priority || "Medium");
}
function ProjectViewModel() {
var self = this;
this.priorityOptions = ko.observableArray(["High", "Medium", "Low"]);
this.projects = ko.observableArray();
this.editing = ko.observable(new Project());
this.addProject = function() {
this.projects.push(this.editing());
this.editing(new Project());
};
}
ko.applyBindings(new ProjectViewModel(), document.body);
<div>
<div>
<label>
Title:
<input type="text" data-bind="value: editing().title, valueUpdate: 'input'">
</label>
</div>
<div>
<label>
Priority:
<select data-bind='options: priorityOptions, value: editing().priority'></select>
</label>
</div>
<div>
<button type="button" data-bind="click: addProject, enable: editing().title">Add Project</button>
</div>
<hr>
<div>Projects:</div>
<div data-bind="foreach: projects">
<div>
<span data-bind="text: title"></span>
(<span data-bind="text: priority"></span>)
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Knockout Js computed not working in a model

I am trying to modify the following knockout js model binding to fit my requirement: http://jsfiddle.net/zrBuL/291/. The modification is as follows:
HTML:
<label>Male
<input type="radio" name="IsMale" value="a" data-bind="checked:IsMale"/>
</label>
<label>Female
<input type="radio" name="IsMale" value="b" data-bind="checked:IsMale"/>
</label>
<div data-bind="text: ABC()"/>
Javascript:
var vm = {
IsMale: ko.observable(false),
ABC:ko.purelyComputed({
read: function(){
return this.IsMale();
}
},this)
};
ko.applyBindings(vm);
For framework, I would favor knockout.js 3.0.0.
The problem is it is not displaying anything for the div above where div's text property is bound to ABC().
Hint: If I replace following line in read function:
return this.IsMale();
with the following line:
return "Hi";
then it works like a charm.
Is there anything I am missing in calling the property IsMale?
After changing purelyComputed to pureComputed and upgrading to at least Knockout 3.2.0 you get to another problem.
this in an object definition is the global window object.
So to get a reference to vm instead of window you need to switch to a function constructor for it:
function MyVM() {
this.IsMale = ko.observable(false);
this.ABC = ko.pureComputed({
read: function(){
return this.IsMale();
}
},this);
};
ko.applyBindings(new MyVM());
Then it will work.
An alternative way of defining this would be:
var vm = {
IsMale: ko.observable(false)
};
vm.ABC = ko.pureComputed({
read: function(){
return this.IsMale();
}
}, vm);
I think the problem lies with two things:
There is no purelyComputed, it is pureComputed
Also pureComputed as introduced within Knockout 3.2.0, so you will need to upgrade to that version in order to use it.
Pure computed observables

DataPicker not getting binded to textbox ? fiddle provided

Well in other cases i will get datepicker binded to my textbox which will be straight forward but not in this case .
Fiddle link : http://jsfiddle.net/JL26Z/1/ .. while to setup perfect seanrio i tried but unable to bind datepicker to textboxes . except that everything is in place
My code :
**<script id="Customisation" type="text/html">** // here i need to have text/html
<table style="width:1100px;height:40px;" align="center" >
<tr>
<input style="width:125px;height:auto;" class="txtBoxEffectiveDate" type="text" id="txtEffective" data-bind="" />
</tr>
</script>
The above code is used for my dynamic generation of same thing n no of time when i click each time on a button . So above thing is a TEMPLATE sort of thing .
My knockout code :
<div data-bind="template:{name:'Customisation', foreach:CustomisationList},visible:isVisible"></div>
<button data-bind="click:$root.CustomisatioAdd" >add </button>
I tried same old way to bind it with datepicker
$('#txtEffective').datepicker(); // in document.ready i placed
Actually to test this i created a textbox with some id outside script with text/html and binded datepicker to it and It is working fine sadly its not working for the textbox inside text/html and i want to work at any cost.
PS: well i haven't posted my view model as it is not required in this issue based senario
View model added with Js
var paymentsModel = function ()
{
function Customisation()
{
var self = this;
}
var self = this;
self.isVisible = ko.observable(false);
self.CustomisationList = ko.observableArray([new Customisation()]);
self.CustomisationRemove = function () {
self.CustomisationList.remove(this);
};
self.CustomisatioAdd = function () {
if (self.isVisible() === false)
{
self.isVisible(true);
}
else
{
self.CustomisationList.push(new Customisation());
}
};
}
$(document).ready(function()
{
$('#txtEffective').datepicker();
ko.applyBindings(new paymentsModel());
});
Any possible work around is appreciated
Regards
The best way I've found to do this is create a simple bindingHandler.
This is adapted from code I have locally, you may need to tweak it...
** code removed, see below **
Then update your template:
** code removed, see below **
By using a bindingHandler you don't need to try to hook this up later, it's done by knockout when it databinds.
Hope this is helpful.
EDIT
I created a fiddle, because I did indeed need to tweak the date picker binding quite a lot. Here's a link to the Fiddle, and here's the code with some notes. First up, the HTML:
<form id="employeeForm" name="employeeForm" method="POST">
<script id="PhoneTemplate" type="text/html">
<div>
<span>
<label>Country Code:</label>
<input type="text" data-bind="value: countryCode" />
</span>
<span><br/>
<label>Date:</label>
<input type="text" data-bind="datepicker: date" />
</span>
<span>
<label>Phone Number:</label>
<input type="text" data-bind="value: phoneNumber" />
</span>
<input type="button" value="Remove" data-bind="click: $parent.remove" />
</div>
</script>
<div>
<h2>Employee Phone Number</h2>
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}">
</div>
<div>
<input type="button" value="Add Another" data-bind="click: add" />
</div>
</div>
</form>
Note I removed the id=... from in your template; because your template repeats per phone number, and ids must be unique to be meaningful. Also, I removed the datepicker: binding from the country code and phone number elements, and added it only to the date field. Also - the syntax changed to "datepicker: ". If you need to specify date picker options, you would do it like this:
<input type="text" data-bind="datepicker: myObservable, datepickerOptions: { optionName: optionValue }" />
Where optionName and optionValue would come from the jQueryUI documentation for datepicker.
Now for the code and some notes:
// Adapted from this answer:
// https://stackoverflow.com/a/6613255/1634810
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {},
observable = valueAccessor(),
$el = $(element);
// Adapted from this answer:
// https://stackoverflow.com/a/8147201/1634810
options.onSelect = function () {
if (ko.isObservable(observable)) {
observable($el.datepicker('getDate'));
}
};
$el.datepicker(options);
// set the initial value
var value = ko.unwrap(valueAccessor());
if (value) {
$el.datepicker("setDate", value);
}
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
$el = $(element);
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') === 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
var current = $el.datepicker("getDate");
if (value - current !== 0) {
$el.datepicker("setDate", value);
}
}
};
function Phone() {
var self = this;
self.countryCode = ko.observable('');
self.date = ko.observable('');
self.phoneNumber = ko.observable('');
}
function PhoneViewModel() {
var self = this;
self.PhoneList = ko.observableArray([new Phone()]);
self.remove = function () {
self.PhoneList.remove(this);
};
self.add = function () {
self.PhoneList.push(new Phone());
};
}
var phoneModel = new PhoneViewModel();
ko.applyBindings(phoneModel);
Note the very updated binding handler which was adapted from this answer for the binding, and this answer for handling onSelect.
I also included countryCode, date, and phoneNumber observables inside your Phone() object, and turned your model into a global variable phoneModel. From a debugger window (F12 in Chrome) you can type something like:
phoneModel.PhoneList()[0].date()
This will show you the current value of the date.
I notice that your form is set up to post somewhere. I would recommend instead that you add a click handler to a "Submit" button and post the values from your phoneModel using ajax.
Hope this edit helps.
Dynamic entities need to have datepicker applied after they are created. To do this I'd use an on-click function somewhere along the lines of
HTML
<!-- Note the id added here -->
<button data-bind="click:$root.CustomisatioAdd" id="addForm" >add </button>
<script>
$(document).on('click', '#addForm', function(){
$('[id$="txtEffective"]').datepicker();
});
</script>

Knock Out data binding not working

I'm having a problem with the knockout.js library. I am using the following code, and the databindings are not executing properly.
HTML Code:
<form data-bind="submit: LogintoSite">UserId:
<input type="email" data-bind="value: UserLogin" />Password:
<input type="password" data-bind="value: Password" />
<button type="submit">Login</Button>
</form>
JavaScript:
var LoginScreenViewModel = function () {
var self = this;
self.UserLogin = ko.observable("Hello");
self.Password = ko.observable("");
self.LoginToSite = function () {
alert("You Pushed the button");
};
};
ko.ApplyBindings(new LoginScreenViewModel());
The project itself is in MVC 4 but i have tried this code on jsfiddle as well and it does not work there either. I can't figure out why it will not work. I am assuming this is something simple i forgot in my code.
Thanks!
You have LoginToSite in your viewmodel while in your databindings you have LogintoSite, notice the lowercase "t".
As somebody else mentioned in the comments I think you should also be calling applyBindings instead of ApplyBindings

Categories