I'm trying to write a basic form using KnockoutJS using the following code:
var Form = function() {
var self = this;
self.name = {
value: ko.observable(""),
isValid: ko.computed(function () {
return self.name.value().length > 0;
}),
},
self.email = {
value: ko.observable(""),
isValid: ko.computed(function () {
return false;
})
},
self.message = {
value: ko.observable(""),
isValid: ko.computed(function () {
return false;
})
},
self.isValid = ko.computed(function () {
return self.name.isValid && self.email.isValid && self.message.isValid;
})
};
However, when I try to run this code I get the following error: Unable to get property 'value' of undefined or null reference. To me, this looks like a scope issue, but I'm not familiar enough with Knockout to understand why this is happening. Without Knockout I seem to be able to get this code working (replacing all observables with true for value and functions for isValid), but I'm looking to get these values updated in real-time. I could always separate out the validation functions to top-level functions but it seems like the improper way to do this. Each form field has a value and it's own unique validation, so it makes sense to make each form field it's own object with both properties.
Any help or guidance is appreciated.
This
self.name = {
value: ko.observable(""),
isValid: ko.computed(function () {
return self.name.value().length > 0;
}),
},
doesn't work because the inner function (the computed callback) refers to self.name, which is not even completely defined yet. Since computeds are called immediately, you see an error.
You could use the deferEvaluation option to delay evaluating the isValid computed until something actually requests its value:
self.name = {
value: ko.observable(""),
isValid: ko.computed({
read: function () { return self.name.value().length > 0; },
deferEvaluation: true
}),
},
That would work, but it would also quickly start to become repetitive and unwieldy.
As an alternative you can use Knockout extenders to make an observable validatatable in an abstract/decoupled fashion.
ko.extenders.validator = function (target, validationCallback) {
// create an isValid property on the target observable,
// immediately calculate validity of its current value
target.isValid = ko.observable(validationCallback(target()));
// subscribe to future changes
target.subscribe(function (newValue) {
target.isValid(validationCallback(newValue));
});
// return target observable for chaining
return target;
};
Now with this extender defined, all you need to do is create callback functions that validate a value and return true or false.
var Form = function () {
var self = this;
self.name = ko.observable("").extend({
validator: function (value) { return value.length > 0; }
});
self.email = ko.observable("").extend({
validator: function (value) { return true; }
});
self.message = ko.observable("").extend({
validator: function (value) { return true; }
});
self.isValid = ko.computed(function () {
var overallValid = true;
ko.utils.objectForEach(self, function (key, value) {
if (value.hasOwnProperty("isValid")) {
overallValid = overallValid && value.isValid();
}
});
return overallValid;
});
};
Further separating your validation functions now becomes very simple:
var validators = {
email: function (value) { return /* email check here */; },
minLen: function (minLen) {
return function (value) { return value.length >= minLen; }
},
maxLen: function (maxLen) {
return function (value) { return value.length <= maxLen; }
},
minmaxLen: function (minLen, maxLen) {
return function (value) { return value.length >= minLen && value.length <= maxLen; }
},
matches: function (regex) {
return function (value) { return regex.test(value); }
}
}
and
self.name = ko.observable("").extend({ validator: validators.minLen(1) });
self.age = ko.observable("").extend({ validator: validators.matches(/^\d+$/) });
You will find that of course somebody already did all of this (and much more).
It's best to create separate functions for each of your objects:
var Form = function() {
var self = this;
self.name = new NameFormField();
}
var NameFormField = function() {
var self = this;
self.value = ko.observable("");
self.isValid = ko.computed(function () {
//not sure why you are checking length here
return self.value().length > 0;
});
}
The other way that you could get it to work is by doing this:
var Form = function() {
var self = this;
self.name = {
value: ko.observable("")
};
self.name.isValid = ko.computed(function () {
//not sure why you are checking length here
return self.name.value().length > 0;
});
Related
I am studying JavaScript closure.
I want to modularize with closure.
So I wrote the code and I did not get the results I wanted
I want different results for result box1 and box2. But for some reason getting same results.
what should I do?
var spinBox = function() {
var spinBoxConfig;
return {
create: function(config) {
spinBoxConfig = {
value: typeof config.value === 'number' ? config.value : 200
}
return this;
},
getValue: function() {
return spinBoxConfig.value;
}
}
}()
var box1 = spinBox.create({
value: 30
});
var box2 = spinBox.create({
value: 310
});
console.log(box1.getValue()); // same
console.log(box2.getValue()); // same
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
You're creating a closure once, when you define the spinbox object. Everything that calls create or getValue will be interacting with that single instance of spinBoxConfig. If you want to create brand new closures every time you call create, you'll need to do so in your create function.
var spinBox = {
create: function (config) {
var spinBoxConfig = {
value: typeof config.value === 'number' ? config.value : 200
}
return {
getValue: function () {
return spinBoxConfig.value;
}
}
}
}
var box1 = spinBox.create({
value: 30
});
var box2 = spinBox.create({
value: 310
});
console.log(box1.getValue());
console.log(box2.getValue())
Though really, that spinBoxconfig is kind of overkill, since you already have the config param in your closure and it has all the relevant data. So you could do this:
var spinBox = {
create: function (config) {
if (typeof config !== 'number') {
config = 200;
}
return {
getValue: function () {
return config;
}
}
}
}
var box1 = spinBox.create(30);
var box2 = spinBox.create(310);
console.log(box1.getValue());
console.log(box2.getValue())
How to create a simple web page that check dirty value with knockout js?
ps:simple code written
This should be plenty to get you started, note the author is rniemeyer:
http://jsfiddle.net/rniemeyer/dtpfv/?utm_source=website&utm_medium=embed&utm_campaign=dtpfv
//not used in this example. one time flag, that drops its subscriptions after the first change.
ko.oneTimeDirtyFlag = function (root) {
var _initialized;
//one-time dirty flag that gives up its dependencies on first change
var result = ko.computed(function () {
if (!_initialized) {
//just for subscriptions
ko.toJS(root);
//next time return true and avoid ko.toJS
_initialized = true;
//on initialization this flag is not dirty
return false;
}
//on subsequent changes, flag is now dirty
return true;
});
return result;
};
ko.dirtyFlag = function(root, isInitiallyDirty) {
var result = function() {},
_initialState = ko.observable(ko.toJSON(root)),
_isInitiallyDirty = ko.observable(isInitiallyDirty);
result.isDirty = ko.computed(function() {
return _isInitiallyDirty() || _initialState() !== ko.toJSON(root);
});
result.reset = function() {
_initialState(ko.toJSON(root));
_isInitiallyDirty(false);
};
return result;
};
function Item(id, name) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.dirtyFlag = new ko.dirtyFlag(this);
}
var ViewModel = function(items) {
this.items = ko.observableArray([
new Item(1, "one"),
new Item(2, "two"),
new Item(3, "three")
]);
this.save = function() {
alert("Sending changes to server: " + ko.toJSON(this.dirtyItems));
};
this.dirtyItems = ko.computed(function() {
return ko.utils.arrayFilter(this.items(), function(item) {
return item.dirtyFlag.isDirty();
});
}, this);
this.isDirty = ko.computed(function() {
return this.dirtyItems().length > 0;
}, this);
};
ko.applyBindings(new ViewModel());
I have a javascript object with 3 booleans:
var obj = {};
obj.isPrimary = true;
obj.isPromotion = false;
obj.isSocial = false;
Only one of these can be true, there can never be a case where more than 1 is true. How can I achieve this?
Using a getter / setter should do the trick:
var obj = {
set isPrimary(bool){
this.Primary = bool;
if(bool){
this.Promotion = this.Social = false;
}
},
set isSocial(bool){
this.Social = bool;
if(bool){
this.Promotion = this.Primary = false;
}
},
set isPromotion(bool){
this.Promotion = bool;
if(bool){
this.Primary = this.Social = false;
}
},
get isPrimary(){ return this.Primary; },
get isSocial(){ return this.Social; },
get isPromotion(){ return this.Promotion; }
}
obj.isPrimary = true;
obj.isSocial = true;
obj.isPromotion = true;
alert(obj.isPrimary + ' ' + obj.isSocial + ' ' + obj.isPromotion);
// false false true (So only `obj.isPromotion` is true)
You can use Object.defineProperty something like:
Object.defineProperty(obj, 'isPrimary', {
get: function(){
return !this.isPromotion && !this.isSocial; //<-- Your logic goes here.
}
});
Of course the logic behind toggling this option on and off it is up to you.
You can simulate this with a function like this
function createToggleValues() {
var options = {
"primary": 1,
"promotion": 2,
"social": 4
},
value = 0;
return {
"set": function(name) {
value = options[name];
},
"is": function(name) {
return (value & options[name]) === options[name];
}
}
};
var togglable = createToggleValues();
togglable.set("primary");
console.log(togglable.is("primary"));
// true
If you require more options to be added, then you might want to simply extend the options object with new values and the next multiple of 2.
Closures are your friend. They prevent that somebody can fiddle with the internal state.
function createPropObj() {
var props = 0;
return {
get isPrimary() { return props === 1; },
set isPrimary(val) { if (val) { props = 1; }; },
get isPromotion() { return props === 2; },
set isPromotion(val) { if (val) { props = 2; }; },
get isSocial() { return props === 4; },
set isSocial(val) { if (val) { props = 4; }; }
}
}
You can use it like that:
> var test = createPropObj();
undefined
> test.isPrimary = true;
true
> test.isPromotion = true;
true
> test.isPrimary
false
You could create an object responsible for allowing a single flag to be true at a time. That would relieve your obj from implementing that logic. In the example below, SingleTrueFlagGroup holds onto a collection of flags, allowing a single one to be true at a time. Your obj can then double-dispatch on an instance of this object to get the job done.
The advantage of a similar implementation is that adding and removing flags becomes trivial.
var flags = ['isPrimary', 'isPromotion', 'isSocial'],
obj = {
_flags: new SingleTrueFlagGroup(flags)
};
flags.forEach(function (flag) {
Object.defineProperty(obj, flag, {
get: function () { return this._flags.get(flag); },
set: function (value) { this._flags.set(flag, value); }
});
});
obj.isPrimary = true;
obj.isPromotion = true;
console.log(obj.isPrimary); //false
console.log(obj.isPromotion); //true
function SingleTrueFlagGroup(flags) {
this._flags = {};
(flags || []).forEach(this.add.bind(this));
}
SingleTrueFlagGroup.prototype = {
constructor: SingleTrueFlagGroup,
set: function (flagToSet, value) {
var flags = this._flags;
(flags[flagToSet] = !!value) && Object.keys(flags).forEach(function (flag) {
if (flag !== flagToSet) flags[flag] = false;
});
},
get: function (flag) { return this._flags[flag]; },
add: function (flag) { this._flags[flag] = false; }
};
The other answers may work, but they seem over-engineered*.
If the values are exclusive, you should represent them in a single property, not three separate booleans:
obj.type = 'primary' // or 'promotion' or 'social'
Assignment among different values is inherently exclusive, since the variable can only hold one value at a time.
Or if you don't like magic strings, you can use an enumeration of defined values:
const TYPE_PRIMARY = 0
const TYPE_PROMOTION = 1
const TYPE_SOCIAL = 2
...
obj.type = TYPE_PRIMARY
validation works fine if validation properties are placed after "HasError" property in VM.
In the case that the property placed before HasError I will get "parameters.hasError" as undefined. I think it's because the property "HasError" is not defined to that time.
Is there any solution without changing the order of the properties inside VM to make it work.
Thanks!
self._BusTypeDefault = function(param) {
var ret = param.BusType;
if(typeof(ret)==='undefined') {
ret = '';
}
else if(ko.isObservable(ret)) {
ret = ret.peek();
}
return ret;
};
self.BusType = ko.observable(self._BusTypeDefault(init)).extend({maxLength: {message: $Resources.PCIBUSError(), maxFieldLength: 255,hasError: self.HasError }});
self._HasErrorDefault = function(param) {
var ret = param.HasError;
if(typeof(ret)==='undefined') {
ret = false;
}
else if(ko.isObservable(ret)) {
ret = ret.peek();
}
return ret;
};
self.HasError = ko.observable(self._HasErrorDefault(init)).extend({errorAggregation: {}});
ko.extenders.maxLength = function (target, parameters) {
//add some sub-observables to our observable
target.hasMaxLengthError = ko.observable();
target.validationMessageMaxError = ko.observable();
//define a function to do validation
function validate(newValue) {
var preValue = target.hasMaxLengthError();
if (newValue.length >= parameters.maxFieldLength) {
target.hasMaxLengthError(true);
target.validationMessageMaxError(parameters.message || "This field is required");
}
else {
target.hasMaxLengthError(false);
target.validationMessageMaxError("");
}
if (parameters.hasError != null && target.hasMaxLengthError() !== preValue && typeof preValue !== 'undefined') {
parameters.hasError(target.hasMaxLengthError());
}
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
You can use a function to delay the interpretation of hasError:
this.myObservable = ko.observable(1).extend({ myExtender : { hasError: function () { return self.hasError } } });
Then in the extender you'll need to call the function to actually get the observable behind:
ko.extenders.myExtender = function (target, params) {
function validate(newValue) {
alert("New Value: " + newValue + " ; Has Error: " + params.hasError()());
}
target.subscribe(validate);
}
See this example: http://jsfiddle.net/7ywLN/
In the name of keeping things DRY, I'd like to ask what the typical approach is when trying to avoid declaring duplicate properties. I have two viewModels: set and folder. Here they are:
Folder:
var folderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.isHovering = ko.observable(false);
self.showCheckbox = function () {
self.isHovering(true);
};
self.hideCheckbox = function () {
self.isHovering(false);
};
self.checkboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 || self.isHovering();
}, self);
self.softCheckboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 && self.isHovering() == false;
}, self);
self.canDrag = ko.computed(function () {
if (selectedItemsCount() == 0 && !isAddingNewContent()) {
return true;
} else {
return false;
}
}, self);
self.isSelected = ko.observable(false);
self.toggleSelected = function () {
self.isSelected(!self.isSelected());
};
self.textSelected = ko.observable(false);
self.toggleTextSelected = function () {
self.textSelected(!self.textSelected());
};
self.isSet = ko.observable(false);
self.isDeleting = ko.observable(false);
self.isNew = ko.observable(false);
// If the folder hasn't been created yet, it won't have a folderId
if (typeof self.folderId === 'undefined') {
self.isNew(true);
}
self.isEditing = ko.observable(false).publishOn("IS_EDITING_CONTENT");
// monitor for clicks
// temp title
self.oldTitle = ko.observable();
};
Set:
var setViewModel = function (data) {
var self = this;
// Checkbox controls
self.isHovering = ko.observable(false);
self.showCheckbox = function () {
self.isHovering(true);
};
self.hideCheckbox = function () {
self.isHovering(false);
};
self.checkboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 || this.isHovering();
}, self);
self.softCheckboxIsVisible = ko.computed(function () {
return selectedItemsCount() > 0 && this.isHovering() == false;
}, self);
self.canDrag = ko.computed(function () {
if (selectedItemsCount() == 0 && !isAddingNewContent()) {
return true;
} else {
return false;
}
}, self);
self.isSelected = ko.observable(false);
self.toggleSelected = function () {
self.isSelected(!self.isSelected());
};
self.textSelected = ko.observable(false);
self.toggleTextSelected = function () {
self.textSelected(!self.textSelected());
};
self.isSet = ko.observable(true);
ko.mapping.fromJS(data, {}, self);
self.isDeleting = ko.observable(false);
self.isNew = ko.observable(false);
// If the folder hasn't been created yet, it won't have a folderId
if (typeof self.setId === 'undefined') {
self.isNew(true);
}
self.isEditing = ko.observable(false).publishOn("IS_EDITING_CONTENT");
// temp title
self.oldTitle = ko.observable();
};
A lot of these properties are duplicated between the viewModels. Should I just keep them as is, or is there a nice way to condense this code?
Create a helper method that both viewmodel constructors call to add all of the common properties...
var helper = function (self, data) {
self.isHovering = ko.observable(false);
// ...
return self;
};
var setViewModel = function (data) {
var self = helper(this, data);
// extra stuff
};
var folderViewModel = function (data) {
var self = helper(this, data);
// extra stuff
};
What about trying inheritance? You could program a prototype viewModel with the properties and functions that both (set and folder) have and then define new "classes" for setViewModel and folderViewModel that have the same prototype as viewModel, just added the properties and functions that only they have.
Introduction to inheritance in javascript can be found here...