Applying multiple extenders in knockout - javascript

I have the following javascript code
function AppViewModel(){
this.myValue = ko.observable().extend({ minNumber: "5"}).extend({ maxNumber: "20" });
}
ko.extenders.minNumber = function(target, minValue){
target.hasError = ko.observable();
target.errorMessage = ko.observable();
function validate(newValue){
target.hasError(parseInt(newValue) < parseInt(minValue) ? true : false);
target.errorMessage(parseInt(newValue) < parseInt(minValue) ? "MinVal" : "");
}
validate(target());
target.subscribe(validate);
return target;
};
ko.extenders.maxNumber = function(target, maxValue){
target.hasError = ko.observable();
target.errorMessage = ko.observable();
function validate(newValue){
target.hasError(parseInt(newValue) > parseInt(maxValue) ? true : false);
target.hasError(parseInt(newValue) > parseInt(maxValue) ? "MaxVal" : "");
}
validate(target());
target.subscribe(validate);
return target;
};
ko.applyBindings(new AppViewModel());
and the following HTML
<input data-bind="value: myValue, valueUpdate: 'afterkeydown'"/><br/>
<span data-bind="text: myValue"></span>
<span data-bind="text: myValue.errorMessage"></span>
<span data-bind="text: myValue.hasError"></span>
What I am trying to achieve is a validation on an observable with a minimum and maximum integer value. My code works http://jsfiddle.net/Gazzo100uk/nCtpx/5/ but I am unsure as to why it works for instance why does the maxNumber not clear the errorMessage property in its validate function even if the integer is less than 5 in this example or vice versa for the min.
What order will these functions be fired?
Like I say, it does what I want it to do but I don't understand how it is working and to be honest I never expected it to work.
Can anybody shed any light please?
Regards,
Gary

I think that the main issue that was causing it to "work" is that you were not setting errorMessage in the maxNumber extender, so it was not being cleared inappropriately:
function validate(newValue){
target.hasError(parseInt(newValue) > parseInt(maxValue) ? true : false);
target.hasError(parseInt(newValue) > parseInt(maxValue) ? "MaxVal" : "");
}

Related

Angular10 - Change multiple css id with onClick event

I am having some trouble trying to change the css of a button with the (click) event. I managed to do it, but the problem is that I have 10 buttons, so I can't depend on one variable in the .ts because once it changes, it will affect all 10 buttons and not just the one which was clicked, so the only thing I thought was having 10 different variables, but it is not quite elegant. Is there any way of doing it a bit cleaner?
Here is what I've got so far:
html:
<button (click)="b1 = !b1" class="tarea" [id]="cambiaId(b1)"></button>
<button (click)="b2 = !b2" class="tarea" [id]="cambiaId(b2)"></button>
<button (click)="b3 = !b3" class="tarea" [id]="cambiaId(b3)"></button>
[...]
<button (click)="b10 = !b10" class="tarea" [id]="cambiaId(b10)"></button>
ts:
export class TareasComponent {
b1 : boolean = false;
b2 : boolean = false;
b3 : boolean = false;
[...]
b10 : boolean = false;
cambiaId(b : boolean){
if (b) {
return "done";
}else{
return "todo";
}
}
I think you might have some valid reason to have duplicate id for elements. To do it in performant way in angular would be to do with ngFor and trackby. A sample imlementation is available at CodeSandbox Implementation
buttons: ButtonType[] = Array(10)
.fill("todo")
.map((value, i) => ({ id: i, value }));
trackById(index: number, button: ButtonType) {
return button.id;
}
buttonClicked(index: number, button: ButtonType) {
const ret = this.buttons.slice(0);
ret[index] = {
...button,
value: button.value ? "done" : "todo"
};
this.buttons = ret;
}
<ng-container *ngFor="let item of buttons; index as i; trackBy:trackById">
<button [id]="item.value" (click)="buttonClicked(i, item)" class="tarea">
{{item.value}}
</button>
</ng-container>

Angular 2 - Get removed character from input

Is there anyway to detect removed character from a text using ngModel in Angular 2 ?
I want something like:
Original text: #Hello World !
Modified text : Hello World !
Console.log
Removed character: '#'
I've found a cool example on Javascript with Jquery by Arie Xiao below:
https://stackoverflow.com/a/17005983/5668956
But I wonder if I can use another thing beside Jquery, as I find that Jquery is pretty hard to implement in Angular 2
I would suggest a Forms controller for acting on changes on the input field. The snippet displays the character add or deleted from the input field.
Check out my Plunker to see the code running.
#Component({
selector: 'app-root',
template: `
<input type="text" [formControl]="form" class="form-control" >
<h3 *ngIf="initial">
Text <span [style.color]="textadd ? 'green' : 'red'" > {{textadd ? "add" : "removed" }} </span>: {{change}}
</h3>
`,
})
export class App {
form;
textadd;
text = "#Hello World!";
initial = false;
change;
ngOnInit() {
this.form = new FormControl({ value: this.text, disabled: false });
this.form.valueChanges.subscribe(val => {
let change;
if (val.length > this.text.length) {
change = val;
for (let variable of this.text) {
change = change.replace(variable, '');
this.textadd = true;
}
} else {
change = this.text;
for (let variable of val) {
change = change.replace(variable, '');
this.textadd = false;
}
}
this.change = change;
this.text = val;
this.initial = true;
});
}
}

Angular 1: Last checkbox doesn't stay checked after page refresh

I am saving all data into localStorage. When a checkbox is checked function is called to change items state. It works fine. However after page refresh, last checked item gets unchecked (or if it was unchecked, it gets checked) while others are working just fine. Why does that 1 last action gets ignored after page is refreshed?
Here is codepen: http://codepen.io/kunokdev/pen/vGeEoY?editors=1010
(add few items and click on "click me" for all of them and then refresh page, last action will be ignored)
The view:
<div ng-app="TaskApp" ng-controller="ToDoCtrl">
<form>
<input type="text" ng-model="toDoItem">
<input type="submit" ng-click="addToDoItem()">
</form>
<div>
<ul>
<div
ng-repeat="item in toDoItems |
orderBy: 'createdAt'
track by item.createdAt">
<b>Content:</b> {{item.content}} <br>
<b>Completed?</b> {{item.completed}}
<md-checkbox ng-model="item.completed" ng-click="toggleToDoItem(item.completed)" aria-label="todo-checkbox">
CLICK ME
</md-checkbox>
</div>
</ul>
</div>
</div>
And JS:
var ls = {};
ls.get = function(key) {
return JSON.parse(localStorage.getItem(key));
};
// sets or updates a value for a key
ls.set = function(key, val) {
localStorage.setItem(key, JSON.stringify(val));
};
// returns true if value is set, else false
ls.isSet = function(key) {
var val = ls.get(key);
return ( null === val || 'undefined' === typeof val) ? false : true;
};
// removes a set item
ls.remove = function(key) {
localStorage.removeItem(key)
};
var TaskApp = angular.module('TaskApp', [
'ngMaterial',
'taskAppControllers'
]);
var taskAppControllers = angular.module('taskAppControllers',[]);
taskAppControllers.controller('ToDoCtrl', ['$scope',
function($scope){
//
loadToDoItems = function(){
var data = ls.get("toDoData");
if (data == null) data = [];
return data;
};
//
$scope.toDoItems = loadToDoItems();
//
$scope.addToDoItem = function(){
var toDoItems = $scope.toDoItems;
var newToDoItem = {
"content" : $scope.toDoItem,
"createdAt" : Date.now(),
"completed" : false
}
toDoItems.push(newToDoItem);
ls.set("toDoData", toDoItems);
$scope.toDoItem = "";
};
//
$scope.toggleToDoItem = function(item){
console.log('test');
var toDoItems = $scope.toDoItems;
for (var i = 0; i < toDoItems.length; i++)
if (toDoItems[i].createdAt === item){
if (toDoItems[i].completed == true)
toDoItems[i].completed = false;
else
toDoItems[i].completed = true;
}
ls.set('toDoData', toDoItems);
};
//
}]);
md-checkbox is designed to toggle whatever you put in ng-model so with your code, md-checkbox was toggling the completed property and then you were changing it back again in your $scope.toggleToDoItem function. Why this worked for all the items except the last clicked I am unsure.
So I changed the ng-click to only save the items to local storage and still got the same problem which leads to me believe the problem is caused by using ng-click on an md-checkbox.
<md-checkbox ng-model="item.completed" ng-click="saveToLocalStorage()" aria-label="todo-checkbox">
CLICK ME
</md-checkbox>
$scope.saveToLocalStorage = function() {
ls.set('toDoData', $scope.toDoItems);
};
So I removed the ng-click and set up a watch on $scope.toDoItems.
<md-checkbox ng-model="item.completed" aria-label="todo-checkbox">
$scope.$watch("toDoItems", function() {
ls.set("toDoData", $scope.toDoItems);
}, true);
Codepen
-- EDIT --
Just read the documentation and feel like an idiot, you should use ng-change instead of ng-click. From the docs regarding ng-change:
Angular expression to be executed when input changes due to user interaction with the input element.
That being said, the above about not needing to toggle the completed property yourself still stands.
You are passing item.completed (in the HTML) to your toggleToDoItem(item) method. In your array loop, you then compare the item.Created field to the item.completed parameter. This is comparing a Date type to a Bool. How is that supposed to work?

Knockout Input ReadOnly State

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);
}

Negate a Property in Knockout

I am actually working with knockout and want to know if there is a way in which i can inverse the knockout property. I have a function IsArchived and want to create a inverse of that, named NotArchived. But I am having issues with it.
The main issue is that I am not seeing any difference in my output. for example there's total of 2000 account in my system out of which its showing 1500 accounts as archived and 2000 account as non archived. Instead of that it should show only 500 non archived accounts.
<li>
<label id="isArchived">
<input type="checkbox" data-bind="checked: isArchived" /><span>Archived</span>
</label>
</li>
<li>
<label id="NotArchived">
<input type="checkbox" data-bind="checked: NotArchived" /><span>Not Archived</span>
</label>
</li
JavaScript:
function WorkersViewModel() {
var self = this;
var initialized = false;
self.isArchived = ko.observable(false);
self.NotArchived = ko.observable(true);
};
self.navigateToSearch = function(uriArray) {
if (self.isArchived()) {
uriArray.push({
isArchived: true
});
}
if (self.NotArchived()) {
uriArray.push({
NotArchived: false
});
}
self.runSearch = function() {
var parameters = {
IsArchived: self.isArchived() ? true : null,
NotArchived: self.isArchived() ? false : null,
};
You can do it by using a computed.
function WorkersViewModel() {
var self = this;
var initialized = false;
self.isArchived = ko.observable(false);
self.NotArchived = ko.computed({
read: function(){ return !self.isArchived() },
write : function(value) { self.isArchived(!value); }
});
};
Depending from the evaluation sequence you need, you may use:
computed observable
subscription
because the solution with a computed observable has been already posted, here is a snippet which uses a subscription:
self.isArchived = ko.observable(false);
self.isNotArchived = ko.observable(true);
self.isArchived.subscribe(function(newValue) {
self.isNotArchived(!newValue);
});
Anozher difference is that the computed observable will be evaluated also for the first time when the view model is instantiated, whereby by using the subscription you should provide to both observables the correct initial value.

Categories