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?
Related
I use the following form and script to let users filter a td table on the input they give in. It filters the rows of the table and only shows the rows corresponding to their given value. They can update the rows that they are seeing, after they do this the page refreshes/reloads to refresh the table. After the page is refreshed/reloaded the search filter shows all rows again. I am searching for a way to keep the rows that they had before the update event happend based on their filter input. In other words, as if the refresh never happend.
Search form;
...
<p align='left' style="display:inline">
<table class="userprof" align='left'>
<tr>
<td class="footer">Filter:
<input type="text" id="myInput" name="filter" style="color:black !important;" placeholder="Filter table" onkeyup='saveValue(this);' />
</td>
</tr>
</table>
</p>
...
I use the folowing script to save their input as localstorage.
...
document.getElementById("myInput").value = getSavedValue("myInput"); // set the value to this input
/* Here you can add more inputs to set value. if it's saved */
//Save the value function - save it to localStorage as (ID, VALUE)
function saveValue(e) {
var id = e.id; // get the sender's id to save it .
var val = e.value; // get the value.
localStorage.setItem(id, val); // Every time user writing something, the localStorage's value will override .
}
//get the saved value function - return the value of "v" from localStorage.
function getSavedValue(v) {
if (!localStorage.getItem(v)) {
return ""; // You can change this to your defualt value.
}
return localStorage.getItem(v);
}
...
I use the following script to filter the table rows
...
function filterTable(event) {
var filter = event.target.value.toUpperCase();
var rows = document.querySelector("#myTable tbody").rows;
for (var i = 0; i < rows.length; i++) {
var nameCol = rows[i].cells[1].textContent.toUpperCase();
var rankCol = rows[i].cells[2].textContent.toUpperCase();
var rankerCol = rows[i].cells[5].textContent.toUpperCase();
var typeCol = rows[i].cells[6].textContent.toUpperCase();
var emailCol = rows[i].cells[3].textContent.toUpperCase();
if (nameCol.indexOf(filter) > -1 || rankCol.indexOf(filter) > -1 || rankerCol.indexOf(filter) > -1 || typeCol.indexOf(filter) > -1 || emailCol.indexOf(filter) > -1) {
rows[i].style.display = "";
} else {
rows[i].style.display = "none";
}
}
}
document.querySelector('#myInput').addEventListener('keyup', filterTable, false);
...
You are almost there and only need minor modifications to make this happen.
I'd suggest that you change your flow up a bit.
First remove the onkeyup inline listener from your HTML. You are currently listening for that event 3 times on 1 element which seems overkill.
...
<p align='left' style="display:inline">
<table class="userprof" align='left'>
<tr>
<td class="footer">Filter:
<input type="text" id="myInput" name="filter" style="color:black !important;" placeholder="Filter table" />
</td>
</tr>
</table>
</p>
...
Then modify the filterTable to accept just a value, not an event object. This way you can call filterTable at any time and inject a value into it. And it allows you to call it immediately with the stored value when the page loads so that your initial filter will be set (or not if there is nothing stored).
Now listen for the keyup event with only a single listener which will both pass the value of the event to filterTable and the event itself to saveValue so that are both filtering and saving.
// Store the input in a variable for reference.
var myInput = document.getElementById("myInput");
var savedValue = getSavedValue("myInput");
// Immediately filter the table and set the input value.
filterTable(savedValue);
myInput.value = savedValue;
//Save the value function - save it to localStorage as (ID, VALUE)
function saveValue(e) {
var id = e.id; // get the sender's id to save it .
var val = e.value; // get the value.
localStorage.setItem(id, val); // Every time user writing something, the localStorage's value will override .
}
//get the saved value function - return the value of "v" from localStorage.
function getSavedValue(v) {
if (!localStorage.getItem(v)) {
return ""; // You can change this to your default value.
}
return localStorage.getItem(v);
}
function filterTable(value) {
console.log(value);
var filter = value.toUpperCase();
var rows = document.querySelector("#myTable tbody").rows;
for (var i = 0; i < rows.length; i++) {
var nameCol = rows[i].cells[1].textContent.toUpperCase();
var rankCol = rows[i].cells[2].textContent.toUpperCase();
var rankerCol = rows[i].cells[5].textContent.toUpperCase();
var typeCol = rows[i].cells[6].textContent.toUpperCase();
var emailCol = rows[i].cells[3].textContent.toUpperCase();
if (nameCol.indexOf(filter) > -1 || rankCol.indexOf(filter) > -1 || rankerCol.indexOf(filter) > -1 || typeCol.indexOf(filter) > -1 || emailCol.indexOf(filter) > -1) {
rows[i].style.display = "";
} else {
rows[i].style.display = "none";
}
}
}
myInput.addEventListener('keyup', function(event) {
var value = event.target.value;
saveValue(event);
filterTable(value);
});
I am having two dropdown (PrimarySpeciality,PrimarySubSpeciality), based on the value in one dropdown(PrimarySpeciality) the other dropdown(PrimarySubSpeciality) value should change.
On Load, I want the 'PrimarySubSpecialities' on load to default value.
How can i do it?
my cshtml:
<div class="nmc-righttab" style="width:265px;">
#Html.DropDownListFor(m => m.User.PSpecialty, Model.PSpecialties, new { id = "ddUserDetails", style = "width:245px;height:25px;", data_bind = "event: {change: primaryChanged}" }, Model.IsReadOnly)
</div>
<div style="width:265px;">
#Html.DropDownListFor(m => m.User.PSubSpecialty,Model.PSubspecialties, new { id = "ddUserDetailsPSubSpeciality", style = "width:245px;height:25px;", data_bind = "options: pSubSpecialities,optionsText: 'Name',optionsValue: 'Id',value:PSubspecialty,enable:isPSpecialitySelected" })
</div>
My JS File:
this.PSubspecialty = ko.observable($('#ddUserDetails').val());
this.pSubSpecialities = ko.observableArray([]);
this.isPSpecialitySelected = ko.observable(false);
this.pSpecilaityChanged = function () {
var pSpecialityVal = $("#ddUserDetails").val();
if (pSpecialityVal) {
model.isPSpecialitySelected(true);
pStartIndex = 0;
pSubSpecialityUrl = '/User/GetSpec?pSpeciality=' + pSpecialityVal +'&sSpeciality=';
loadPSubSpecilaities();
}
else
{
model.isSelected(false);
}
};
On Load,I want to set initial value for 'pSubSpeciality' to be text as '<>' with value as '0'.
Even I am able to add item to Model.PSubSpecialties,but could not be able to display the added item in psubspeciality dropdown.
How can set the initial value to pSubSpecialityDropdown from Model.PSubSpecialities.
Work within your model more, and with the UI less. Instead of making a changed event on the DropDownList items, subscribe to changes on a value-bound variable. In your PrimarySpecialty DropDown,
data_bind = "value: primarySpecialty"
Then in your JS:
var self = this;
this.primarySpecialty = ko.observable();
this.isPrimarySpecialtySelected = ko.pureComputed(function () {
return !!self.primarySpecialty();
});
this.primarySubSpeciality = ko.observable();
//...
this.primarySpecialty.subscribe(function (newValue) {
if (newValue) {
loadPrimarySubSpecialities();
this.primarySubSpeciality('0'); // Set default, by value
}
});
If the PrimarySubSpeciality dropdown has an option whose Id member is '0', the item will be selected in it.
I m trying to do a workaround for a bug. i need to just change the css of an element when an other checkbox is clicked. But it is not working.. It just works when i click on an other button somewhere else but when i click on the checkbox the view is not being refreshed maybe ?
Any ideas ?
View:
<input
type="checkbox"
value="application.callback" id="telefonBox"
ng-click="application.callback = !application.callback; toggleClass(application.callback)"
/>
Controller:
$scope.toggleClass = function(newValue) {
var element = angular.element(document.querySelector('#additional'));
if (newValue) {
element.toggleClass("tooltip-agent tooltip-agentChecked ");
} else {
element.toggleClass("tooltip-agentChecked tooltip-agent");
}
$scope.$apply();
}
i tried this to but not working
$scope.$watch('$scope.application.callback', function (newValue, oldValue) {
var element = angular.element(document.querySelector('#additional'));
if (newValue) {
element.toggleClass("tooltip-agent tooltip-agentChecked ");
} else {
element.toggleClass("tooltip-agentChecked tooltip-agent ");
}
add ng-modal into checkbox
<input type="checkbox" ng-model="application.callback">
and ng-class into your #additional element
<div id="additional" ng-class="{true:'tooltip-agent tooltip-agentChecked', false:'tooltip-agentChecked tooltip-agent'}[application.callback]"></div>
DEMO
You should not manipulate elements in angular as much as possible, you can do it easier with ng-class like this
<div id="test" ng-class='{ active: vm.isChecked }'>
lorem
</div>
ng-class accepts an object as parameter, in this case it's { active: vm.isChecked } which mean if vm.isChecked evaluates to true, the active class will be applied to the element
$scope.selection = function($event) {
var checkbox = $event.target;
var action = (checkbox.checked ? 'check' : 'uncheck');
if(action == "check")
angular.element(document.querySelector('#additional')).addClass("tooltip-agent tooltip-agentChecked");
else
angular.element(document.querySelector('#additional')).addClass("tooltip-agentChecked tooltip-agen");
};
});
<input type="checkbox" id="additional" ng-model="check" ng-click="selection($event)" >
I need to build an ng-repeat list from values typed and submitted using html inputs. It works, I can return the item from the input using ng-repeat, but when editing the input for the next item, the input value is still bound to the value in the ng-repeat, and then changes that value instead of adding a completely new one. I'm sure I'm missing something simple, but stuck at the moment.
How do I add new items that are not binded to the input on each ng-click?
HTML:
<div ng-controller="MyCtrl">
Hello, {{name}}!
<br/>
<input ng-model='newitem1' />
<input ng-model='newitem2' />
<input ng-model='newitem3' />
<button ng-click='add()'>Add</button>
<br/>
<b>Items Added Below</b>
<div select-last ng-repeat='item in items'>
<div ng-model='item' id='item-{{$index}}' class='input-{{$index}}'>{{newitem1}} {{newitem2}} {{newitem3}}</div>
</div>
Javascript:
var myApp = angular.module('myApp',[]);
myApp.directive('selectLast', function () {
return function (scope, element, attrs) {
if (scope.$last=== true) {
console.log("the last element is here");
}
}
});
function MyCtrl($scope) {
$scope.name = 'Please try entering something and click Add button';
$scope.items = [];
$scope.newitem1 = '';
$scope.newitem2 = '';
$scope.newitem3 = '';
$scope.add = function(){
$scope.items.push($scope.newitem1,$scope.newitem2,$scope.newitem3);
}
}
You could clone the item using angular.copy() so it is a copy of the data, however not a direct reference to it.
This will allow you to continually add new items.
function MyCtrl($scope) {
$scope.name = 'Please try entering something and click Add button';
$scope.items = [];
$scope.newitem1 = '';
$scope.newitem2 = '';
$scope.newitem3 = '';
$scope.add = function(){
var item1 = angular.copy($scope.newitem1),
item2 = angular.copy($scope.newitem2),
item3 = angular.copy($scope.newitem3);
$scope.items.push(item1, item2, item3);
}
}
I realized that I was using the input model for my expression.
{{newitem1}}
Instead of
{{item.newitem1}}
Fixed
This is the html:
<span ng-repeat="category in categories">
<label class="checkbox-inline" for="{{category.id}}">
<input type="checkbox" ng-checked="check(category.id)" ng-click="saveRoleMenu($event,category.id)" name="group" id="{{category.id}}" />
{{category.name}}
</label>
</span>
this is my controller:
//get all the menus
authorityService.getMenus().then(function(response) {
$scope.categories = response.data.data; //get all the menus success
})
//get the user's menus
var roleId = sessionStorage.getItem("roleId");
authorityService.getMenusByRoleId(roleId).then(function(response) {
$scope.userMenu = response.data.data; //get the user's menu success
})
$scope.saveRoleMenu = function($event, id) {
var checkbox = $event.target;
if (checkbox.checked) {
var roleId = $location.search().id;
authorityService.saveRoleMenu(roleId, id).then(function(response) {
if (response.data.code == 0) {
alert("success!");
}
})
}
}
$scope.check = function(value) {
//error caused here
for (var i = 0; i < $scope.userMenu.length; i++) {
if(value == $scope.userMenu[i].id){
return true;
}else{
return false;
}
}
}
I want to set checkbox checked if the value in $scope.userMenu,but in the check function,caused the error "Cannot read property 'length' of undefined",what's the reason about this error?
Cannot read property 'length' of undefined
means $scope.userMenu is not defined. Define $scope.userMenu before assigning value to it.
$scope.userMenu = [];
var roleId = sessionStorage.getItem("roleId");
Its because your userMenu are loaded from API and the check function execute before they are loaded.
define the userMenu in top something like this -
$scope.userMenu = [];
This would stop check from throwing errors. Once menu are loaded from API view will re-render with it.