I have a div bound to an observable array like so:
<div data-bind="foreach: currentSelected().Tags " contenteditable="true">
<span data-bind="text:$data"></span>
</div>
I made the div's content editable so that any changes (user input) get reflected in the Tags array, but that isn't working as I assumed. There seems to be no automatic push for observable arrays. My question is, how do I get new values inside the Tags array using binding?
Here is how I am setting currentSelected:
var newBlogEntry = new NewBlogEntry();
var newBlogEntryObservable = new NewBlogEntryObservable(newBlogEntry);
self.currentSelected(newBlogEntryObservable);
The function for NewBlogEntry is as follows:
function NewBlogEntry()
{
return { "Id": 0, "Title": "Title", "Description": "Description", "Tags": [] };
}
I managed to solve the problem by adding an extra input field:
<input type="text" placeholder="new tag" data-bind="value:newTag, valueUpdate: 'afterkeydown', enterkey: addNewTag" id="addTag" />
The binding for the enter key is as follows:
ko.bindingHandlers.enterkey = {
init: function (element, valueAccessor, allBindings, viewModel) {
var callback = valueAccessor();
$(element).keypress(function (event) {
var keyCode = (event.which ? event.which : event.keyCode);
if (keyCode === 13) {
callback.call(viewModel);
return false;
}
return true;
});
}
};
And this is the observable for the newTag:
self.newTag = ko.observable();
self.addNewTag = function () {
if (self.newTag() == '')
alert("Enter something in input field");
else {
var tag = self.newTag();
self.currentSelected().Tags.push(tag);
$("#addTag").val("");
}
}
I have an own binding for numeric inputs made in knockoutJS which accepts only numbers.
To make big numbers I declare various instances of number in a NumberField like:
var NumberField = function () {
var self = this;
self.maskFormat = "0";
self.firstNumber = ko.observable("");
self.secondNumber = ko.observable("");
self.thirdNumber = ko.observable("");
};
And
<input id="0" maxlength="1" type="tel" data-bind="numeric: firstNumber">
<input id="1" maxlength="1" type="tel" data-bind="numeric: secondNumber">
<input id="2" maxlength="1" type="tel" data-bind="numeric: thirdNumber">
This is working like a charm, but when I made submission, system is expecting a map with numbers. I achieved it IMHO in an ugly way:
Added to NumberField this attribute:
this.cleanNumber = ko.pureComputed(function () {
return this.firstNumber().toString() + this.secondNumber().toString() + this.thirdNumber().toString();
}, this);
And in the code, when I need to use it I must do this:
let unwrapNumbers = this.numbers().cleanNumber().split("").map(function (item){
return Number(item);
});
This is working, but... I'm pretty sure there is an easier and more straight way.... Any suggestions?
I think it could help to split the computed in to two parts:
Getting the numbers you want to include in order
Creating a string based on the ordered values
Often it makes sense to split a computed in to several pure computeds that have a single clear data processing responsibility.
var NumberField = function () {
var self = this;
self.firstNumber = ko.observable(1);
self.secondNumber = ko.observable(2);
self.thirdNumber = ko.observable(3);
self.orderedNumbers = ko.pureComputed(function() {
return [self.firstNumber,
self.secondNumber,
self.thirdNumber].map(ko.unwrap);
});
self.cleanedNumber = ko.pureComputed(function() {
return self.orderedNumbers().join("");
});
};
var nf = new NumberField();
// If you want the numbers:
console.log(nf.orderedNumbers());
// If you want the string
console.log(nf.cleanedNumber());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Now, I'm not sure what your requirements are, but you can take it one step further and use an observableArray as the base data format:
var NumberField = function () {
var self = this;
self.numbers = ko.observableArray(
[ko.observable(0), ko.observable(1), ko.observable(2)]);
self.add = function() {
self.numbers.push(ko.observable(self.numbers().length));
}
self.unwrappedNumbers = ko.pureComputed(function() {
return self.numbers().map(ko.unwrap);
});
self.cleanedNumber = ko.pureComputed(function() {
return self.unwrappedNumbers().join("");
});
};
ko.applyBindings(new NumberField());
label { display: block }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: numbers">
<label>
<span data-bind="text: 'Number ' + $index()"></span>
<input type="number" data-bind="textInput: $parent.numbers()[$index()]">
</label>
</div>
<button data-bind="click: add">add</button>
<pre>
Unwrapped:<code data-bind="text: unwrappedNumbers"></code>
Cleaned:<code data-bind="text: cleanedNumber"></code>
</pre>
I have a filter function that I would like to adapt to user input, but I don't really know how to do it. I'm fairly new to Knockout so I would appreciate some help with this.
When I click the filter button (see code below) i get this in the input area:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
observable.valueWillMutate();
observable[observableLatestValue] = arguments[0];
observable.valueHasMutated();
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return observable[observableLatestValue];
}
}
What I want to achieve is to write a value in the input area, click the button (for now, will use submit later) and have the search results filtered. The array employeeList is an observable array that is populated through an ajax call (the search function).
KO Code:
self.employeeList = ko.observableArray([]);
self.currentFilter = ko.observable();
self.filterEmpl = ko.computed(function () {
if (!self.currentFilter()) {
return self.employeeList();
} else {
return ko.utils.arrayFilter(self.employeeList(), function (employee) {
return employee.DepartmentName == self.currentFilter();
});
}
});
self.filter = function (value) {
self.currentFilter(value);
} //filter
HTML:
<form>
<input type="text" placeholder="Department" id="department" class="filterInput" data-bind="value: currentFilter" />
<button data-bind="click: function () { filter(currentFilter) }">Filter</button>
<br />
<input type="text" placeholder="Office" class="filterInput" />
<br />
<input type="text" placeholder="Skills" class="filterInput lastInput" />
</form>
Thanks!
Your filterEmpl is a ko.computed. This means it automatically updates once one of the observable values it uses is updated.
In your case, it will update whenever either self.employeeList or self.currentFilter changes.
To try this out, type one of the DepartmentNames in the example below. Once you remove focus from the input, the value data-bind updates currentFilter, and self.filterEmpl is updated.
var VM = function() {
self.employeeList = ko.observableArray([
{ DepartmentName: "Test1", Name: "Employee 1" },
{ DepartmentName: "Test2", Name: "Employee 2" }
]);
self.currentFilter = ko.observable();
self.filterEmpl = ko.computed(function() {
if (!self.currentFilter()) {
return self.employeeList();
} else {
return ko.utils.arrayFilter(self.employeeList(), function(employee) {
return employee.DepartmentName == self.currentFilter();
});
}
});
}
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Type "Test1" or "Test2" and blur focus to filter</h2>
<form>
<input type="text" placeholder="Department" data-bind="value: currentFilter" />
</form>
<h2>All employees:</h2>
<ul data-bind="foreach: employeeList">
<li data-bind="text: Name"></li>
</ul>
<h2>Filtered employees:</h2>
<ul data-bind="foreach: filterEmpl">
<li data-bind="text: Name"></li>
</ul>
Now, if you want to filter only when a button is pressed, you don't need the ko.computed. You define a second ko.observableArray and write to it from within the filter function. Note that you don't need to pass it any arguments; the viewmodel is already aware of the currentFilter value via the value binding.
var VM = function() {
self.employeeList = ko.observableArray([
{ DepartmentName: "Test1", Name: "Employee 1" },
{ DepartmentName: "Test2", Name: "Employee 2" }
]);
self.currentFilter = ko.observable();
self.filterEmpl = ko.observableArray(self.employeeList());
self.filter = function() {
var result = self.employeeList(),
filter = self.currentFilter();
if (filter) {
result = ko.utils.arrayFilter(result, function(employee) {
return employee.DepartmentName == filter;
});
}
self.filterEmpl(result);
};
}
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Type "Test1" or "Test2" and tap button to filter</h2>
<form>
<input type="text" placeholder="Department" data-bind="value: currentFilter" />
<button data-bind="click: filter">filter</button>
</form>
<h2>All employees:</h2>
<ul data-bind="foreach: employeeList">
<li data-bind="text: Name"></li>
</ul>
<h2>Filtered employees:</h2>
<ul data-bind="foreach: filterEmpl">
<li data-bind="text: Name"></li>
</ul>
Personally, I like to use the computed approach. You can extend the observable using the rateLimit option if performance is limiting. Ultimately, it's mostly a UX decision.
P.S. The input you did get in the input area is knockout's definition of the ko.observable function. In your <button>, you pass currentFilter without getting its value using currentFilter(). In filter, you write this to currentFilter which is data-bound to the <input>. I figured it'd be more useful to explain the two approaches, but you still might want to know where the strange input came from...
I have a list of checkbox displayed using ng-repeat, now i need to disable some checkbox based on specific value checkbox is checked.
checkbox display code
<div class="checkbox">
<label ng-repeat="specific in specifications">
<input ng-click="onClick(specific, newObject[specific])" id="specifications_chk" value="{{specific}}" ng-model="newObject[specific]" type="checkbox">
{{specific}}
</label>
</div>
Here am using ng-click function to disable checkbox.
This is my controller code:
$scope.onClick = function(sp, checked){
if(sp == 'Sofa_Cleaning' && checked === true){
angular.element(document.getElementById('specifications_chk'))[0].disabled = true;
}
if(sp == 'Sofa_Cleaning' && checked === false){
angular.element(document.getElementById('specifications_chk'))[0].disabled = false;
}
};
html view:
in controller code am able disable only first value (IndepthHomeCleaning), so how can i disable Room_Cleaning, Kitchen_Cleaning, and Carpet_cleaning when Sofa_Cleaning is checked
Am i stuck with this, Help will be really appreciated
I don't know why you are still stick with the jquery. You are getting
by id when you are trying to disable the checkbox.
.
In html, id should be unique for the current html page. y ou are
having duplicated ids and you are getting the first value from the
array using [o] when you are trying to disable.
.
If you strictly want solution from the same method then use the
following methodology Use class name instead of id for the check boxes and use
angular element and disable.
The following code should be used where you need to disable. Use appropriate code based on your need.
angular.forEach(document.getElementsByClassName('checkbox1'), function(value, key) {
angular.element(value)[0].disabled = true;
debugger
});
If you want to change specifications array you need a new array and mapping.
Define new row object for each specification.
Access specification row and its prop (is active or is disable) from your specification.
http://jsfiddle.net/ms403Ly8/87/
function testCtrl($scope, $filter) {
$scope.specifications = ["IndepthHomeCleaning", "Room_Cleaning", "Kitchen_Cleaning", "Carpet_cleaning", "Sofa_Cleaning"];
$scope.specificationRows = [];
$scope.newRow = function(spec) {
var newSpec = {
SpecName: spec,
IsDisabled: false,
IsClicked: false
};
$scope.specificationRows.push(newSpec);
};
$scope.getRowObject = function(sp) {
return $filter('filter')($scope.specificationRows, {
SpecName: sp
})[0];
};
$scope.getRowStatus = function(spec) {
console.log($filter('filter')($scope.specificationRows, {
SpecName: spec
})[0].IsDisabled);
return $scope.getRowObject(spec).IsDisabled;
};
$scope.onClick = function(sp) {
$scope.getRowObject(sp).IsClicked = !$scope.getRowObject(sp).IsClicked;
if (sp == 'Sofa_Cleaning') {
$scope.getRowObject("Carpet_cleaning").IsDisabled = $scope.getRowObject(sp).IsClicked;
$scope.getRowObject("Kitchen_Cleaning").IsDisabled = $scope.getRowObject(sp).IsClicked;
$scope.getRowObject("Room_Cleaning").IsDisabled = $scope.getRowObject(sp).IsClicked;
}
};
}
getRowObject is example. Maybe it's not best practice. You can change this function.
You should not have same id for multiple elements in your code. You need to make you elements ids unique.
HTML :
<div ng-app ng-controller="LoginController">
<div class="checkbox">
<label ng-repeat="specific in specifications">
<input id="specifications_chk{{$index}}" ng-click="onClick(specific, newObject[specific])" value="{{specific}}" ng-model="newObject[specific]" type="checkbox" /> {{specific}}
<br>
</label>
</div>
</div>
JavaScript :
function LoginController($scope) {
$scope.specifications = ['IndepthHomeCleaning',
'Room_Cleaning',
'Kitchen_Cleaning',
'BathroomCleaning',
'Carpet_cleaning',
'Sofa_Cleaning'
];
$scope.newObject = {
'IndepthHomeCleaning': 1,
'Room_Cleaning': 2,
'Kitchen_Cleaning': 3,
'BathroomCleaning': 4,
'Carpet_cleaning': 5,
'Sofa_Cleaning': 6
};
$scope.onClick = function(sp, checked) {
if (sp == 'Sofa_Cleaning' && checked === true) {
angular.element(document.getElementById('specifications_chk1'))[0].disabled = true;
angular.element(document.getElementById('specifications_chk2'))[0].disabled = true;
angular.element(document.getElementById('specifications_chk4'))[0].disabled = true;
}
if (sp == 'Sofa_Cleaning' && checked === false) {
angular.element(document.getElementById('specifications_chk1'))[0].disabled = false;
angular.element(document.getElementById('specifications_chk2'))[0].disabled = false;
angular.element(document.getElementById('specifications_chk3'))[0].disabled = false;
}
};
}
JsFiddle : https://jsfiddle.net/nikdtu/f7k3kcwn/
use ng-disabled
var angApp = angular.module('myApp', []);
angApp.controller('MyController', function MyController($scope) {
$scope.specifications = [{"text":"IndepthHomeCleaning ", "disabled":false, "checked":false},
{"text":"Room_Cleaning", "disabled":false, "checked":false},
{"text":"Kitchen_Cleaning", "disabled":false, "checked":false},
{"text":"BathroomCleaning", "disabled":false, "checked":false},
{"text":"Carpet_cleaning", "disabled":false, "checked":false},
{"text":"Sofa_Cleaning", "disabled":false, "checked":false}];
$scope.onClick = function(sp, checked){
console.log(sp, checked);
if(sp.text == 'Sofa_Cleaning' && checked){
$scope.specifications[1].disabled = true;
$scope.specifications[2].disabled = true;
$scope.specifications[4].disabled = true;
}
if(sp.text == 'Sofa_Cleaning' && !checked){
$scope.specifications[1].disabled = false;
$scope.specifications[2].disabled = false;
$scope.specifications[4].disabled = false;
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<div class="checkbox">
<label ng-repeat="specific in specifications" style="display:block">
<input ng-disabled="specific.disabled" ng-change="onClick(specific, specific.checked)" id="specifications_chk" value="specific.checked}" ng-model="specific.checked" type="checkbox">
{{specific.text}}
</label>
</div>
</div>
I think you're over complicating this. Your data object should be simplified to look like this
$scope.specifications = [
{name: 'Sofa_Cleaning', active: false},
{name: 'Carpet_Cleaning', active: false},
{name: 'Room_Cleaning', active: false},
{name: 'Kitchen_Cleaning', active: false},
{name: 'BathroomCleaning', active: false}
];
Template:
<input type="checkbox"
ng-bind="specific.name"
ng-model="specific.active"
ng-disabled="shouldDisable(specific)">
in your controller
$scope.disableWhenSofaIsChecked = ['Room_Cleaning','Kitchen_Cleaning','Carpet_Cleaning'];
$scope.shouldDisable = function(specific) {
var disable = $scope.disableWhenSofaIsChecked;
if(disable.indexOf(specific.name) > -1) {
for(var obj in $scope.specifications) {
if(obj.name === 'Sofa_Cleaning') {
return obj.active;
}
}
}
return false;
}
EDIT
So if you're getting specifications data from API, you can just do this:
SomeApi
.get()
.then(function(results){
// Assuming it's a promise. End result is the same for a passed-in callback
// Assuming results is an array of names
$scope.specifications = results.map(function(item){
return {
name: item,
active: false
};
});
});
You are using 'id' attribute which should be unique for each DOM element. So definitely your solution will take single element. So you should try using class attribute and document.getElementbyClass function.
Don't use jquery to for dom manipulation.
Use ng-change instead of ng-click. This will be the appropriate one.
Here I've update a plunkr.
Here for the simple example, I've done like
if the value is check5 then disable all the checkboxes. Otherwise enable the checkboxes.
https://jsfiddle.net/AvGKj/517/
Note: I've used little bit old plugin. Please take the logics alone
<div ng-controller="MyCtrl">
<table>
<tr ng-repeat="appObj in myAppObjects">
<td>
<input type="checkbox" ng-model="appObj.cb1"
ng-disabled="appObj.disable"
ng-change="Disable(appObj,myAppObjects)">
{{appObj.value}}
</td>
</tr>
</table>
{{myAppObjects}}
<br>list of checked items: {{checkedItems()}}
</div>
function MyCtrl($scope) {
$scope.myAppObjects = [
{id:1,value:'check1'},
{id:2,value:'check2'},
{id:3,value:'check3'},
{id:4,value:'check4'},
{id:5,value:'check5'}
];
$scope.Disable=function(appObj,list)
{
if(appObj.value=="check5")
{
for(var i=0; i<list.length;i++)
{
if(list[i].value!="check5")
{
if(appObj.cb1==true)
{
list[i].disable=true;
}
else{
list[i].disable=false;
}
}
}
}
}
}
I'm very new to Knockout JS and I'm having a hell of a time trying to get this project completed.
I've created a map website which displays a series of pins on the page for popular locations around town. The idea is that the search bar to the left of the page will filter out pins on the map with names that do not match the search query. There is also a "master" list on the left of the page that the search bar will filter from too.
I used an example I found on jsfiddle here: http://jsfiddle.net/mythical/XJEzc/ but I'm having troubles applying that same logic to my code.
Here it is:
HTML:
<li>
<input class="form-control" placeholder="Search…" type="search" name="filter" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</li>
<ul data-bind="template: {name:'pin', foreach: pins}"></ul>
</ul>
<script type="text/html" id="pin">
<li>
<strong data-bind="text: name"></strong>
</li>
</script>
JS:
self.pins = ko.observableArray([
new self.mapPin("Anchorage Alaska", 61.190491, -149.868937, "test1"),
new self.mapPin("Anchorage Alaska", 61.190491, -149.868937, "test2")
]);
self.query = ko.observable('');
self.filterPins = ko.dependentObservable(function () {
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(name, function (pin) {
return pin.toLowerCase().indexOf(search) >= 0;
});
});
With the logic I've setup if the name is removed from the pin constructor it will remove it from the map.
Here is my somewhat working example: http://jamesiv.es/projects/map/
HTML
<ul data-bind="template: {name:'pin', foreach: pins}"></ul>
change to
<ul data-bind="template: {name:'pin', foreach: filterPins}"></ul>
Javascript
self.filterPins = ko.dependentObservable(function () {
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(self.name, function (pin) {
return pin.toLowerCase().indexOf(search) >= 0;
});
});
change to
self.filterPins = ko.computed(function () {
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(self.pins(), function (pin) {
return pin.name().toLowerCase().indexOf(search) >= 0;
});
});
Just want to update the code if using Knockout version newer than 3.2
Change from value and valueUpdate to textInput, as reccomended here
HTML:
<input class="form-control" placeholder="Search…" type="search" name="filter" data-bind="textInput: query" autocomplete="off" />
JS:
this.query = ko.observable('');
this.filteredPins = ko.computed(function () {
if (this.query()) {
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(this.pins(), function (pin) {
return pin.name().toLowerCase().indexOf(search) >= 0;
});
} else {
return pins
}}, this);