I want change height of texterea by knockout 2.3.0
I bind the value of the texterea to "FileData" observable field
and want the texterea rows attribute will changed to the count of rows in "FileData"
the value bind works fine but attr don't work
var self = this;
self.FileData = ko.observable("");
self.lineBreakCount = function(str) {
/* counts \n */
try {
return ((str.match(/[^\n]*\n[^\n]*/gi).length)) + 1;
} catch (e) {
return 0;
}
}
self.buttonClick = function () {
$.get(url, { })
.success(function (serverData) { self.FileData(serverData);})
}
<button type="button" data-bind="click: buttonClick">Click Me</button>
<textarea readonly="readonly" data-bind="value: FileData, attr: { 'rows': lineBreakCount(FileData)}"></textarea>
Your lineBreakCount expects a string, but you're passing it an observable that contains a string.
To fix this, unwrap your observable either in the binding (lineBreakCount(FileData())), or in the method (str().match)
var VM = function() {
var self = this;
self.FileData = ko.observable("");
self.lineBreakCount = function(str) {
/* counts \n */
try {
return ((str.match(/[^\n]*\n[^\n]*/gi).length)) + 1;
} catch (e) {
return 0;
}
}
self.buttonClick = function() {};
};
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<button type="button" data-bind="click: buttonClick">Click Me</button>
<textarea data-bind="textInput: FileData, attr: { 'rows': lineBreakCount(FileData())}"></textarea>
Related
I have found this very short yet handy two way binding code written in pure JavaScript. The data binding works fine, but what I want is to take the value from the first input and multiply it by a desired number and bind the outcome to the next input. I will appreciate any kind of help.
This is my HTML Code:
<input class="age" type="number">
<input class="age" type="number">
and the JavaScript Code:
var $scope = {};
(function () {
var bindClasses = ["age"];
var attachEvent = function (classNames) {
classNames.forEach(function (className) {
var elements = document.getElementsByClassName(className);
for (var index in elements) {
elements[index].onkeyup = function () {
for (var index in elements) {
elements[index].value = this.value;
}
}
}
Object.defineProperty($scope, className, {
set: function (newValue) {
for (var index in elements) {
elements[index].value = newValue;
}
}
});
});
};
attachEvent(bindClasses);
})();
If the desired results is take first input value, do something with it, put it to second input then why so serious?
(function () {
var bindClasses = ["age"];
var attachEvent = function (classNames) {
classNames.forEach( function( className ) {
var els = document.getElementsByClassName( className ),
from, to;
if ( 2 > els.length ) {
return;
}
from = els[0];
to = els[els.length - 1];
from.addEventListener( 'keyup', function() {
var v = this.value;
// Do whatever you want with that value
// Then assign it to last el
if ( isNaN( v ) ) {
return;
}
v = v / 2;
to.value = v;
});
});
};
attachEvent(bindClasses);
})();
Another simple approach to two-way binding in JS could be something like this:
<!-- index.html -->
<form action="#" onsubmit="vm.onSubmit(event, this)">
<input onchange="vm.username=this.value" type="text" id="Username">
<input type="submit" id="Submit">
</form>
<script src="vm.js"></script>
// vm.js - vanialla JS
let vm = {
_username: "",
get username() {
return this._username;
},
set username(value) {
this._username = value;
},
onSubmit: function (event, element) {
console.log(this.username);
}
}
JS Getters and Setters are quite nice for this - especially when you look at the browser support for this.
I have a json that returns this:
[
{"home": [
{"name":"Federico","surname":"","et":"20","citt":"Milano"},
{"name":"Alberto","surname":"","et":"30","citt":"Milano"},
{"name":"Mirko","surname":"","et":"30","citt":"Roma"},
{"name":"Andrea","surname":"","et":"28","citt":"Firenze"}
]},
{"home": [
{"name":"Brad Pitt"},
{"name":"Tom Cruise"},
{"name":"Leonardo DiCaprio"},
{"name":"Johnny Depp"}
]},
{"home": [
{"name":"","surname":""},
{"name":"","surname":""},
{"name":"","surname":""},
{"name":"","surname":""}
]}
]
When there is a valid value provided for name, for example, I would like to change the background-color of the input box to white. But if the provided value is invalid, I would like to change the background-color back to red.
HTML:
<div class="context">
<div data-bind="foreach: personList">
<button data-bind="text: name,click: $root.getInfoPersona($index()), attr: {'id': 'myprefix_' + $index()}"/>
<button data-bind="text: $index,enable: false"></button>
</div>
<form>
<label>Name: </label>
<input id="idname" data-bind="value: name, css: { changed: name.isDirty(), notchanged : !name.isDirty() }" />
<label>Surname: </label>
<input id="idsurname" data-bind="value: surname, css: { changed: surname.isDirty }" />
<label>Years: </label>
<input id="idyears" data-bind="value: years, css: { changed: years.isDirty }" />
<label>Country: </label>
<input id="idcountry" data-bind="value: country, css: { changed: country.isDirty }" />
<button data-bind="click: save">Save Data</button>
<button data-bind="click: clear">Clear</button>
</form>
</div>
Javascript:
$(document).ready(function(){
ko.subscribable.fn.trackDirtyFlag = function() {
var original = this();
this.isDirty = ko.computed(function() {
return this() !== original;
}, this);
return this;
};
var ViewModel = function() {
var self=this;
var pi= (function(){
var json = null;
$.ajax({
'async': false,
'global': false,
'url': 'persona.json',
'dataType': 'json',
'success': function(data){
json=data;
}
});
return json;
})();
var questionsPerson= pi;
console.log(questionsPerson);
self.personList = ko.observableArray(questionsPerson[0].home);
var n=pi[0].home[0].name;
var c=pi[0].home[0].surname;
var e=pi[0].home[0].et;;
var ci=pi[0].home[0].citt;
self.name = ko.observable(n).trackDirtyFlag();
self.surname = ko.observable(c).trackDirtyFlag();
self.years = ko.observable(e).trackDirtyFlag();
self.country = ko.observable(ci).trackDirtyFlag();
self.save = function() {
alert("Sending changes to server: " + ko.toJSON(self.name));
alert("Sending changes to server: " + ko.toJSON(this));
};
self.clear = function(){
self.name("");
self.surname("");
self.years("");
self.country("");
};
self.getInfoPersona = function(indice){
var i=indice;
var ris= pi;
var n=ris[0].home[indice].name;
var c=ris[0].home[indice].surname;
var e=ris[0].home[indice].et;
var ci=ris[0].home[indice].citt;
self.name(n);
self.surname(c);
self.years(e);
self.country(ci);
self.getinfoPersona = ko.computed( function(){
return self.name() + " " + self.surname() + " " + self.years() + " " + self.country();
});
};
};
ko.applyBindings(new ViewModel());
});
First screenshot: the desired effect.
Second screenshot: the wrong effect.
The effect displayed on the second screenshot happens when I click on the second name to change person. The input box becomes "invalid" with background-color=red instead of background-color=white.
The quickest way to get it working is to modify your trackDirtyFlag extension:
ko.subscribable.fn.trackDirtyFlag = function() {
var original = ko.observable(this()); // make original observable
this.isDirty = ko.computed(function() {
return this() !== original(); // compare actual and original values
}, this);
// this function will reset 'dirty' state by updating original value
this.resetDirtyFlag = function(){ original(this()); };
return this;
};
...and call resetDirtyFlag after you reassigned values for editing:
self.name(n); self.name.resetDirtyFlag();
self.surname(c); self.surname.resetDirtyFlag();
self.years(e); self.years.resetDirtyFlag();
self.country(ci); self.country.resetDirtyFlag();
Look at the fiddle to see how it works.
However in general your approach is pretty far from optimal. Maybe this article will be useful for you.
I continue learning knockout and continue facing weird issues I don't know how to overcome.
I have the following html page and js script:
HTML:
<div data-bind="debug: $data, foreach: objects">
<span hidden="hidden" data-bind="value: type.id"></span>
<input type="text" data-bind="value: type.title" />
<button type="button" data-bind="click: $parent.removeObject">- </button>
</div>
<div class="control-group form-inline">
<select data-bind="options: availableTypes, optionsValue: function(item) {return item;},
optionsText: function(item) {return item.title;}, value: itemToAdd.type,
optionsCaption: 'Select types...'"></select>
<button type="button" data-bind="click: addObject">+</button>
</div>
</div>
JS:
function model() {
var self = this;
var types = [new Type("1"), new Type("2"), new Type("3")];
var objects = [new Object("1")];
self.objects = ko.observableArray(objects);
self.usedTypes = ko.computed(function() {
return types.filter(function(type) {
for (var j = 0; j < self.objects().length; j++) {
if (self.objects()[j].type.id === type.id) {
return true;
}
}
return false;
});
}, self);
self.availableTypes = ko.computed(function() {
return types.filter(function(type) {
for (var j = 0; j < self.usedTypes().length; j++) {
if (self.usedTypes()[j].id === type.id) {
return false;
}
}
return true;
});
}, self);
self.itemToAdd = new Object();
self.addObject = function() {
self.objects.push(self.itemToAdd);
self.itemToAdd = new Object();
};
self.removeObject = function(object) {
self.objects.remove(object);
};
};
function Object(type) {
var self = this;
self.type = new Type(type);
}
function Type(id) {
var self = this;
self.id = id;
self.title = id;
}
ko.applyBindings(new model());
I simplified model to show the error. The thing is that knockout claims it is illegal to call do this:
<span hidden="hidden" data-bind="value: type.id"></span>
Because it can't find property id in context. As far as I can see it is there and everything ok with it.
Could, please, anybody point me at my mistakes?
p.s. Here is a JsFiddle
ADDITION
Thanks to #Daryl's help I was able to localize the issue. If I replace
self.itemToAdd = new Object();
self.addObject = function() {
self.objects.push(self.itemToAdd);
self.itemToAdd = new Object();
};
with:
self.itemToAdd = new Object();
self.addObject = function() {
self.objects.push(new Object(1));
self.itemToAdd = new Object();
};
though, the following code still doesn't work:
self.itemToAdd = new Object("1");
self.addObject = function() {
self.objects.push(self.itemToAdd);
self.itemToAdd = new Object();
};
It seems itemToAdd objects is populated incorrectly from html elements it's binded to. But I still don't know what exactly is wrong.
You've allowed your type dropdown to be unset. When knockout shows the caption, it clears the actual value. This means that, by rendering the UI, your itemToAdd.type is cleared.
Your second approach solves this by not using the data-bound instance.
Furthermore:
I wouldn't overwrite the Object constructor if I were you... Find a different name.
Make sure your itemToAdd has observable properties if you want to do two-way binding to the UI.
I am trying to add a simple functionality in my program and Im having a little trouble figuring out how to do something I wanted.
Here's what I got:
My input textbox, with a link beside it to disable/enable readonly property on that input textbox.
<div>
<input type="text" data-bind="attr: { 'readonly': getreadonlyState() }" value="420" />
Edit
</div>
Here's my knockout script for it:
var ViewModel = function() {
var self = this;
self.getreadonlyState = ko.observable('readonly');
self.readonly = function() {
if (self.getreadonlyState()) {
self.getreadonlyState(undefined);
}
else self.getreadonlyState('readonly');
}
}
ko.applyBindings(new ViewModel());
This works great, but what I wanted is when I click the edit link, it will change the text of the link to something like: "Stop Editing" so when I click "Stop Editing" the readonly property is enabled again.
Here's a fiddle of what Im working on.
Any help will be greatly appreciated, thank you!
Here's an alternative to #thangcao's answer. I'm not saying this is any better or worse, simply an alternative which uses a subscribe handler instead of a computedObservable.
<div>
<input type="text" data-bind="attr: { 'readonly': getreadonlyState() }" value="420" />
</div>
var ViewModel = function() {
var self = this;
self.getreadonlyState = ko.observable('readonly');
self.getreadonlyState.subscribe(function(val) {
self.linkText(val === "readonly" ? "Edit" : "Stop editing");
});
self.readonly = function() {
if (self.getreadonlyState()) {
self.getreadonlyState(undefined);
}
else self.getreadonlyState('readonly');
}
self.linkText = ko.observable("Edit");
}
ko.applyBindings(new ViewModel());
Notice that there's no need for the additional <span> in #thangcao's answer.
Also, why is the "edit"/"stop editing" element an anchor tag? Why not just make it a <span> and do away with the need for the additional inline JavaScript (which you can anyway replace with a return false; inside the readonly function).
http://jsfiddle.net/ajameson/eeTjS/87/
I have updated your Fiddle and hope that it meets your need:
<div>
<input type="text" data-bind="attr: { 'readonly': getreadonlyState() }" value="420" />
<span data-bind="text:linkText"></span>
</div>
var ViewModel = function() {
var self = this;
self.getreadonlyState = ko.observable('readonly');
self.readonly = function() {
if (self.getreadonlyState()) {
self.getreadonlyState(undefined);
}
else {
self.getreadonlyState('readonly');
}
}
self.linkText = ko.computed(function(){
return self.getreadonlyState() == 'readonly' ? "Stopping edit" : "Edit";
}, self);
}
ko.applyBindings(new ViewModel());
You can use this binginHandlers :
ko.bindingHandlers.readOnly = {
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
element.setAttribute("disabled", true);
} else {
element.removeAttribute("disabled");
}
}
};
In my html :
<input type="text" id="create-finess" class="form-control" data-bind="readOnly: _locked" />
Finaly in my JS :
//Constructor of my view model
function ViewModel(resx) {
this._locked = ko.observable();
}
// on init of the page i lock the input
this._load = function () {
this._locked(true);
}
I have an array within an array, for example I have the following objects:
{ruleGroups: [{
rules: [{
dataField1:ko.observable()
,operator:ko.observable()
,dataField2:ko.observable()
,boolean:ko.observable()
,duration:ko.observable()
}]
}]
};
How can I edit the array within the array?
I was able to improve the issue but still have problems with adding row when adding group, the new group works but the old groups run dead:
A working example is found here (http://jsfiddle.net/abarbaneld/UaKQn/41/)
Javascript:
var dataFields = function() {
var fields = [];
fields.push("datafield1");
fields.push("datafield2");
return fields;
};
var operators = function() {
var operator = [];
operator.push("Plus");
operator.push("Minus");
operator.push("Times");
operator.push("Divided By");
return operator;
};
var booleanOperators = function() {
var operator = [];
operator.push("Equal");
operator.push("Not Equal");
operator.push("Greater Than");
operator.push("Less Than");
operator.push("Contains");
operator.push("Etc...");
return operator;
};
var ruleObj = function () {
return {
dataField1:ko.observable()
,operator:ko.observable()
,dataField2:ko.observable()
,boolean:ko.observable()
,duration:ko.observable()
}
};
var ruleGroup = function() {
return rg = {
rules: ko.observableArray([new ruleObj()]),
addRow: function() {
rg.rules.push(new ruleObj());
console.log('Click Add Row', rg.rules);
},
removeRow : function() {
if(rg.rules().length > 1){
rg.rules.remove(this);
}
}
}
};
var ViewModel = function() {
var self = this;
self.datafields = ko.observableArray(dataFields());
self.operators = ko.observableArray(operators());
self.booleanOperators = ko.observableArray(booleanOperators());
self.groupRules = ko.observableArray([new ruleGroup()]);
self.addGroup = function() {
self.groupRules.push(new ruleGroup());
};
self.removeGroup = function() {
if(self.groupRules().length > 1){
self.groupRules.remove(this);
}
};
self.save = function() {
console.log('Saving Object', ko.toJS(self.groupRules));
};
};
ko.applyBindings(new ViewModel());
HTML
<div data-bind="foreach: { data: groupRules, as: 'groupRule' }" style="padding:10px;">
<div>
<div data-bind="foreach: { data: rules, as: 'rule' }" style="padding:10px;">
<div>
<select data-bind="options: $root.datafields(), value: rule.dataField1, optionsCaption: 'Choose...'"></select>
<select data-bind="options: $root.operators(), value: rule.operator, optionsCaption: 'Choose...'"></select>
<select data-bind="options: $root.datafields(), value: rule.dataField2, optionsCaption: 'Choose...',visible: operator"></select>
<select data-bind="options: $root.booleanOperators(), value: rule.boolean, optionsCaption: 'Choose...'"></select>
<input data-bind="value: rule.duration" />
<span data-bind="click: groupRule.addRow">Add</span>
<span data-bind="click: groupRule.removeRow">Remove</span>
</div>
</div>
<span data-bind="click: $parent.addGroup">[Add Group] </span>
<span data-bind="click: $parent.removeGroup">[Remove Group]</span>
</div>
</div>
<div>
<span data-bind="click:save">[Save]</span>
</div>
I was able to fix the issue by rearranging the function of ruleGroup to:
var ruleGroup = function() {
var rg = {
rules: ko.observableArray([new ruleObj()]),
addRow: function() {
rg.rules.push(new ruleObj());
console.log('Click Add Row', rg);
},
removeRow : function() {
if(rg.rules().length > 1){
rg.rules.remove(this);
}
}
}
return rg;
};
Not exactly sure why this made a difference but I think its due to now a new var is being created and referenced.
Working JSFiddle is found here http://jsfiddle.net/abarbaneld/UaKQn/