angular scope object issue - angularjs

PROBLEM:
I created one var object and inside that var object i am referring
to slider scope object. so when I will use my slider it will update
scope object but not update in reference object. i.e
filterObject.filter.priceRange.min
so for changin var object i have to do it manually then it will work
you can see my fiddle so you will understand what i am trying to say because i don't know how to explain my problem.
Workinh example:
http://jsfiddle.net/kevalbhatt18/wk6xhy3k/4/
see what i did:
// this object is for slider
$scope.priceRangeSlider = {
minValue: 100,
maxValue: 10000,
options: {
floor: 100,
ceil: 10000,
step: 1,
translate: function(value) {
return 'Rs ' + value;
}
}
};
//var object refere to scope object
var filterObject = {
filter: {
priceRange: {
min: $scope.priceRangeSlider.minValue // refe not working
},
yearRange: {},
languageValue: [],
formatValue: []
}
};
HOW YOU DEBUG:
slide "price slider" so you can se changeing value below the yearslider, after changing value of price slider click on button below the price slider you will get 100 which is allocated at onload of application

The reason to the problem is that filterObject.filter.priceRange.min will be evaluated only once, when the controller is run for the first time.
One fix is to change it to a function:
var filterObject = {
filter: {
priceRange: {
min:function () { return $scope.priceRangeSlider.minValue; }
...
Then:
$scope.minvaluetest = filterObject.filter.priceRange.min();
Fiddle: http://jsfiddle.net/masa671/9kjqeueo/
UPDATE:
You just need to turn the logic the other way around.
See a new Fiddle: http://jsfiddle.net/masa671/z5fkvh5b/
Now filterObject has the value that can be sent to a server.

Related

How to uncheck a checkbox that gets filtered in ng-repeat

I've been scratching my head on this one for hours worth of troubleshooting and I can't seem to figure it out so was wondering if any of you could help.
I have an array of objects in a json file, and I'm making a filtering menu based on different properties in the file that one can check/uncheck in view to filter the results. The issue I have is to be able to uncheck any items in the menu that hide as a result of not being available in the current results being displayed.
I have a plunker example here: https://plnkr.co/edit/KZmMiSisA1gKyahG5rHF
Sample from plunker:
$scope.list = [
{ parent : 'fruit', type : 'orange' },
{ parent: 'fruit', type : 'apple' },
{ parent : 'fruit', type : 'kiwi' },
{ parent : 'vegetable', type : 'kale' },
{ parent : 'vegetable', type : 'cabbage' }
];
$scope.filtered = $scope.list;
$scope.selectedType = [];
$scope.selectedParent = [];
$scope.$watch(function () {
return {
selectedType: $scope.selectedType,
selectedParent: $scope.selectedParent,
}
}, function (value) {
var filterType = {
parent : $scope.selectedParent,
type : $scope.selectedType,
};
var startFilter = $scope.list;
for (var i in filterType) {
startFilter = filter(startFilter, filterType[i], i);
}
$scope.filtered = startFilter;
}, true);
Basically, if someone selects "fruit" and then "orange", but then unchecks "fruit", I would want "orange" to uncheck as well.
I just checked your plunker. The code on the bottom is very complicated, but I might be able to help you with these snippets.
Add a ng-change attribute to the parents:
<input type="checkbox"
checklist-model="selectedParent"
checklist-value="key"
data="{{::key}}"
ng-change="checkParent(key, checked)"/>
Now you can detect the changes in your controller:
$scope.checkParent = function(parent, checked) {
if (!checked) {
$scope.list.filter(function(fruit) {
return fruit.parent === parent;
}).forEach(function(fruit) {
$scope.selectedType = $scope.selectedType.filter(function(_selectedType) {
return _selectedType != fruit.type;
});
});
}
};
Plunkr
Beware, that this is inefficient, as it filters Selected type for every fruit to be unselected, it can be refactored with some nice functional tools.
But in general I'd change the controller if possible, and create a map with this structure:
{
parent: {
name: "fruit"
selected: false,
children: [{
type: "organge"
selected: false
}]
...
}
This way you can make your controller code much more readable.
Edit:
I was checking the two filter what you wrote. I couldn't come up with a better code as I still think that you should change the data structure. Iterating over and over lists is an expensive process, and both of your filters has two nested for loops. I cannot think of an easy way of getting rid of them with your data structure.
I spent some time on refactoring your code, getting rid of the watches and utilizing lodash. Check the updated Plunk, I hope it helps.
I added this function to your plunker:
$scope.uncheck = function(key){
$scope.selectedType.splice(key)
}
And this to the parent:
<input type="checkbox" checklist-model="selectedParent" checklist-value="key" data="{{::key}}" ng-change="uncheck(key)" />
It works for me if this is in fact what you are trying to accomplish.

Kendo Grid get selected row and assign to local variable

I am using a Kendo Grid with AngularJs and TypeScript. I am able to get the selected row but I cannot assign the result to a local variable as it seems to run in a different scope.
My grid has the following properties:
this.gridOptions = {
dataSource: {
data: this.items,
pageSize: 10
},
selectable: 'row',
filterable: {
mode: 'row'
},
change: this.onItemSelect,
...
}
The typescript function is as follows:
onItemSelect(kendoEvent : any) {
var grid = kendoEvent.sender;
this.selectedItem = grid.dataItem(grid.select());
}
selectedItem is defined in my Controller class:
export class ServicePackageModalController {
private selectedItem : any;
...
}
The problem is the this.selectedItem is undefined when I check it in another button event later on. I assume this is because the scope is different when the function gets called in the Kendo Grid, so 'this' means something else and not my controller class:
handleNext() {
console.debug(this.selectedItem) //this is undefined here?
}
So my question is how do I assign this result to my controller class so I can access it later on?
Only way I could see to solve this was adding the following code to my handleNext function:
var entityGrid = $("#EntitesGrid").data("kendoGrid");
var selectedItem = entityGrid.dataItem(entityGrid.select());
You also need to have an id attribute with the name EntitiesGrid on the kendo-grid directive.

AngularJS : watching a particular property in an array of objects for changes in the property's value

In my custom directive, I'm adding elements to the DOM based on the number of objects in my datasource array. I need to watch a specific property in each object. As I add these elements to the DOM, I want to set up a $watch on the checked property of each object in the toppings array, but it's not working, and I don't know why. I set up a breakpoint inside the function that should be invoked when the property changes from true to false or false to true, but that function is never invoked. Is the reason obvious? I'm just learning Angular, so I could easily be making a stupid error.
$scope.bits = 66; (i.e. onions and olives)
$scope.toppings = [
{ topping: 1, bits: 2, name: 'onions' },
{ topping: 2, bits: 4, name: 'mushrooms' },
{ topping: 3, bits: 8, name: 'peppers' },
{ topping: 4, bits: 16, name: 'anchovies' },
{ topping: 5, bits: 32, name: 'artichokes' },
{ topping: 6, bits: 64, name: 'olives' },
{ topping: 7, bits: 128, name: 'sausage' },
{ topping: 8, bits: 256, name: 'pepperoni' }
]
Each object in the model gets a new checked property which will be true or false.
NOTE: the object array will at most contain a dozen or so items. Performance is not a concern.
link: function link(scope, iElement, iAttrs, controller, transcludeFn) {
<snip>
// At this point scope.model refers to $scope.toppings. Confirmed.
angular.forEach(scope.model, function (value, key) {
// bitwise: set checked to true|false based on scope.bits and topping.bits
scope.model[key].checked = ((value.bits & scope.bits) > 0);
scope.$watch(scope.model[key].checked, function () {
var totlBits = 0;
for (var i = 0; i < scope.model.length; i++) {
if (scope.model[i].checked) totlBits += scope.model[i].bits;
}
scope.bits = totlBits;
});
});
<snip>
Array of Objects:
$scope.toppings = [
{ topping: 1, bits: 2, name: 'onions' },
{ topping: 2, bits: 4, name: 'mushrooms' },
{ topping: 3, bits: 8, name: 'peppers', checked:undefined /*may be*/ }
];
Watch using AngularJs $WatchCollection:
Instead of monitoring objects array above, that can change for any property in the object, we will create an array of properties of the elements for which we are watching the collection (.checked).
We filter the array's elements to only monitor those elements which have .checked defined and map that to an array for angular watchCollection.
When a change fires, I will compare the old and new arrays of (.checked) to get exact changed element using lodash difference method.
$scope.$watchCollection(
// Watch Function
() => (
$scope
.toppings
.filter(tp => tp.checked !== undefined)
.map(tp => tp.checked)
),
// Listener
(nv, ov) => {
// nothing changed
if(nv == ov || nv == "undefined") return;
// Use lodash library to get the changed obj
let changedTop = _.difference(nv,ov)[0];
// Here you go..
console.log("changed Topping", changedTop);
})
You use MAP to collect all of the property values you need + convert them into a small string representation (in this case 1 and 0) and then join them together into a string that can be observed.
A typescript example:
$scope.$watch(
() => this.someArray.map(x => x.selected ? "1" : "0").join(""),
(newValue, oldValue, scope) => this.onSelectionChanged(this.getSelectedItems()));
The watchExpression parameter to $scope.$watch should either be a string or a function. I've not experimented extensively with this (I try and avoid explicit watches where possible) but I think it does also work when you watch 'simple' scope properties as object references, but not so well with more complex references.
I think if you supply the reference as a string, e.g. 'model[' + key + '].checked' then you may have some success (I only say this because I've done something similar with $watchCollection previously).
Alternatively you should be able to supply a function, e.g.
$scope.$watch(function() { return scope.model[key].checked; }, function() { ... });
Hope this helps!
Use $watchCollection instead.
From docs:
$watchCollection(obj, listener);
Shallow watches the properties of an object and fires whenever any of the properties change (for arrays, this implies watching the array items; for object maps, this implies watching the properties). If a change is detected, the listener callback is fired.
The obj collection is observed via standard $watch operation and is examined on every call to $digest() to see if any items have been added, removed, or moved.
The listener is called whenever anything within the obj has changed. Examples include adding, removing, and moving items belonging to an object or array.

Kendo UI grid, issue saving new record (AngularJS)

I have an order line grid with a custom edit form, whose fields are pre-populated for adding a row. I thought I had this working based on help I received from this question:
How to populate add-row form programmatically for Kendo UI grid (AngularJS)
However, though it works in the simplified plunker, there are a couple of issues when trying to implement it in a real project.
Here is an updated plunker to show the issues below:
http://plnkr.co/edit/wtW4RzVu7uuhrJJbWvVd?p=preview
Here is the relevant HTML:
<div id="wrapper" class="container-fluid" ng-controller="ticketEntryController">
<div ng-controller="ticketLineController">
<div kendo-grid="ticketLineGrid" k-options="getTicketLineGridOptions()"></div>
</div>
<button id="addButton" ng-click="addRow()" class="btn btn-primary btn-sm">Add Row</button>
Clicking the addButton button calls $scope.addRow on the ticketEntryController:
(function () {
'use strict';
angular.module('app').controller('ticketEntryController', ticketEntryController);
function ticketEntryController($scope) {
$scope.lineGrid = {};
$scope.addRow = function () {
var item = {
itemNo: "TEST 123",
id: 0,
itemDescr: "new item description",
cat: "CAM",
mfg: "ACME",
mfgPartNo: "ABC123456",
itmStat2: "N",
price: 133,
qty: 1
};
var ticketId = 200;
$scope.$broadcast('AddRow', ticketId, item);
}
}
})();
addRow() above broadcasts to $scope.$on in ticketLineController:
(function () {
'use strict';
angular.module('app').controller('ticketLineController', ticketLineController);
function ticketLineController($scope) {
$scope.$on('AddRow', function(event, ticketId, item) {
console.log("ticketLineController, AddRow: " + item.itemNo);
$scope.ticketId = ticketId;
$scope.itemForAdd = item;
$scope.ticketLineGrid.addRow();
});
$scope.getTicketLineGridOptions = function () {
return {
dataSource: {
type: "json",
transport: {
read: function (options) {
console.log("--- read ---");
options.success(ticketLines);
},
create: function (options) {
console.log("--- create ---");
ticketLines.push(options.data);
options.success(options.data);
},
update: function (options) { // Why is it calling "update" for addRow??
console.log("--- update ---");
ticketLines.push(options.data);
options.success(options.data);
},
destroy:function (options) { // Why is it calling "destroy" for addRow (issue 2)?
console.log("--- destroy ---");
},
},
schema: {
model: {
id: "id",
fields: {
id: { type: "string" },
orderId: { type: "number" },
lineNo: { type: "number" },
...
},
}
},
sort: [{ field: "ItemNo", dir: "asc" }],
pageSize: 50
},
...
edit: function (e) {
if (e.model.isNew()) {
e.model.set("orderId", $scope.ticketId);
e.model.set("lineNo", 0);
e.model.set("id", $scope.ticketId + "_0");
...
e.model.set("qty", 1);
}
var popupWindow = e.container.getKendoWindow();
e.container.find(".k-edit-form-container").width("auto");
popupWindow.setOptions({
width: 640
});
},
Issue #1: When adding a row, "update" is getting called instead of "create" on the grid's dataSource.
Issue #2: After cancelling out of the edit form, the next time you try to add a row, it for some reason calls "destroy" after the "update" To reproduce:
1) Click Add Row
2) Click Cancel in the edit form
3) Click Add Row again
4) Click Update
I heard back from Telerik on this, and the reason "update" was being called instead of "create" is that the id field must be empty for a new record (=0 for integer or "" for string id fields). Once I fixed that, both issues were resolved.
On a related note, the record returned from the server POST (to add record) must contain a populated id field, so that subsequent edits call the "update" instead of "create" in the grid.
I had exactly the same issue. Actually The ID field is autogenerated in my database and the issue was resolved simply by assigning newly created id back to the ViewModel as below:
dbContext.Shipping.Add(entity);
dbContext.SaveChanges();
//int newID = entity.Id;
ShippingViewModel.Id = entity.Id;
Hope this would help.

Adding new HighChart Series

At this code javascrip give an error
$.each(JSON, function(i, array) {
chart.series[i].name = array.teamName;
chart.series[i].setData(array.teamPower, true);
});
I must define the chart.series[i] because it say "Cannot set property 'name' of undefined"
but i can't find a way in order to do this.
Because it fonction runs with requestData so it came after chart determine with options
function showGraph() {
chart = new Highcharts.Chart(option);
}
chart: {
renderTo: 'graphicShow',
type: 'spline',
events: {
load: requestData
}
}
...in option...
title: {
text: 'Power %'
},
series: []
...
You need to looka at the "Methods and Properties" part of the API. See http://api.highcharts.com/highcharts#Chart (There is an jsFiddle on the documentation page as well).
var chart = new Highcharts.Chart(options);
chart.addSeries({
name: array.teamName,
data: array.teamPowher
});
If you are going to add several series you should set the redraw flag to false and then call redraw manually after as that will be much faster.
var chart = new Highcharts.Chart(options);
chart.addSeries({
name: array.teamName,
data: array.teamPower
}, false);
chart.addSeries({
name: array.teamName,
data: array.teamPower
}, false);
chart.redraw();
You too can specify as second option of addSeries a boolean value, that indicates if it redraws or not

Resources