find value in array without looping (angular) - arrays

i have created angular directive which replace string value
applabel directive:
app.directive('applabel',function(){
return{
templateurl:'label.html',
restrict: 'E',
replace: true,
scope:{
'key' : '#',
'default' : '#'
},
link : function(scope,elem,attr){
var data = [{key: Id , value:"ID"},
{ key: name, value: "Name"},
{ key: sts, value: "status"} ];
var keyVal = scope.key;
for(var i = 0;i < data.length ; i++)
{
if(keyVal = data.length[i].key)
{
scope.value = data.length[i].value;
}
else {
scope.value = scope.default;
}
}
}
}
});
my html code is
<applabel key="name" default="Default Name">
</applabel>
label.html
<label>{{value}}</label>
output will be: Name
this code works properly
but the problem i don't want for loop in my code because in data array there is too much data and i am using multiple times this 'applabel' directive in my page so that it takes to much time
any solution that without looping my value is replaced
thanks in advance

If you have data as array the only way to find some data is iterate over, if you want to not iterate you could keep data as hash map( JS Object)

I find underscorejs to be a very useful library to include in my projects. With it you can use one of a few functions to get what you are looking for. One would be _.find
var result = _.find(data, function(x){ return x.key === scope.key; });
scope.value = result.value;

If you can modify the data, you can use an object instead of an array. This way accessing a value is done in constant time
app.directive('applabel',function(){
return{
templateurl:'label.html',
restrict: 'E',
replace: true,
scope:{
'key' : '#',
'default' : '#'
},
link : function(scope,elem,attr){
var data = {Id:{value:"ID"},
name: {value: "Name"},
sts:{ value: "status"} };
var keyVal = scope.key;
scope.value=typeof data[keyVal] !=="undefined" ? data[keyVal].value : scope.default;
}
}
}
});
If you don't need to add more informations to the object you can do
var data = {Id:"ID", name:"Name", sts:"status"};
var keyVal = scope.key;
scope.value=typeof data[keyVal] !=="undefined" ? data[keyVal] : scope.default

This code has several errors, but I guess this is irrelevant.
Instead of using an array of objects each having a key and value fields, use a single object:
{
Id: 'ID',
name: 'Name',
sts: 'status
}
and then simply use
scope.value = data[scope.key] || scope.default;
Another thing that you should do is define the data out of the link function: there is no way to recreate a whole new array/object every time the directive is used.

Related

Why does my angular directed work on one table cell but none others?

I am using a ContextMenu directive within a kendo grid. I have made one change to it so I can include icons in the text (changed $a.text(text) to $a.html(text).
I have one in the first cell (I highjacked the hierarchical cell) that has row operations (add, clone& delete) and one on a span within each cell that changes the cell values operation (addition, subtraction, equals, etc...)
Both of these were working. I am unsure what I changed that stopped it from working because I last checked it several changes ago (I'm still locked out of TFS so I can't revert).
One change I made was to include a disabled/enabled check to the working contextMenu. I tried adding the same to the broken one and no dice.
I do perform a $compile on the working menu and the broken one is only included in the kendo field template.
If I must compile the field template (and I didn't need to before), how can this be done?
So here is some code.
working menu:
$scope.getRowContextMenu = function (event) {
var options =
[[
"<span class='fa fa-files-o'></span>Clone Rule", function (scope, cmEvent) {/*omitted for brevity*/}),rowContextDisableFunction]]
}
var setHierarchyCell = function (grid) {
var element = grid.element;
var hCells = element.find("td.k-hierarchy-cell");
hCells.empty();
var spanStr = "<span context-menu='getRowContextMenu()' class='fa fa-bars'></span>";
hCells.append($compile(spanStr)($scope));
var span = hCells.find("span.fa");
span.on('click', function (event) {
$(this).trigger('contextmenu', event);
});
}
kendo template:
var mutliFormTemplate = function (fieldName, type) {
var result = "";
result += "<span context-menu='getOperationContextMenuItems()' class='fa #= " + fieldName + "_Obj.OperationSymbol # type-" + type + "'> </span>\n";
/*The rest pertains to the cell value. excluded for brevity*/
return result;
}
$scope.getOperationContextMenuItems = function () {
//I trimmed this all the way down to see if I could get it working. Still no joy
return [
["test", function () { }, true]
];
}
Creating the kendo columns dynamically:
$scope.model = {
id: "RuleId",
fields: {}
};
$scope.fieldsLoaded = function (data, fields) {
var column = {}
$.each(fields, function () {
var field = this;
$scope.columns.push({
field: field.Name,
title: field.Name,
template: mutliFormTemplate(field.Name, "selector")
});
column[field.Name ] = { type: getFieldType(field.Type.BaseTypeId) }
});
$scope.model.fields = column;
}
Thanks for any and all help ^_^

ng-class - finding a value inside object

I have an object that looks like this:
$scope.things = [
{
name: 'Bob!',
short_name: 'bob',
info: 'something something'
},
{
name: 'Steve',
short_name: 'steve',
info: 'something something something'
},
];
I loop through them like this and add an ng-click:
<div ng-repeat="thing in things" ng-click="addThing(thing.name, thing.short_name, thing_info" ng-class="thingClass(thing.name)">content goes here</div>
the ng-click="addThing()" basically bunches up the values and adds them to the object.
When clicked, it should add the class selected - this worked fine and dandy when I wasn't using a multidimensional object, because it was simply looking for name inside the object / array (at this point, I think it's an object... but at the time, it was an array)
I can't work out how to do the equivalent to this...
$scope.thingClass= function(name) {
if($scope.thingSelected.indexOf(name) != -1) {
return 'selected';
}
};
...with the object as it now stands. I've tried to adapt a few answers from here that I found through google, such as:
$scope.teamClass = function(name) {
var found = $filter('filter')($scope.thingSelected, {id: name}, true);
if (found.length) {
return 'selected';
}
};
...but with no joy.
Can anyone point / nudge me in the right direction?
You could simply pass the thing object to thingClass:
... ng-class="thingClass(thing)" ...
and implement thingClass as follows:
$scope.thingClass= function(thing) {
return $scope.thingSelected.indexOf(thing) >= 0 ? 'selected' : '';
}
And maybe your should apply this technique to addThing also:
... ng-click="addThing(thing)" ...
$scope.addThing = function(thing) {
if ($scope.thingSelected.indexOf(thing) < 0)
$scope.thingSelected.push(thing);
}
But instead of tracking the selected things in an array its much easier to introduce a selected property in each thing:
$scope.addThing = function(thing) {
thing.selected = true;
}
$scope.thingClass= function(thing) {
return thing.selected ? 'selected' : '';
}

Directive is not updating the view with async data, using controllerAs and bindToController

I'm having some trouble getting a directive to update my view.
In my controller I set the intial values for the attributes of the <tiki-list> directive. Then after, 2 seconds, I'm updating vm.listObjectSelected to test its async behaviour.
However, the view is not reflecting the update.
Controller:
var listObject = [{"display":"display1", "value":"value1"}, {"display":"display2", "value":"value2"}, {"display":"display3", "value":"value3"}]
vm.listObject = listObject
vm.listObjectSelected = [{"display":"display1", "value":"value1"}]
$timeout(function(){
vm.listObjectSelected = [{"display":"display1", "value":"value1"}, {"display":"display3", "value":"value3"}]
}, 2000)
HTML
<tiki-list max="" type="multi" list="editController.listObject" selected="editController.listObjectSelected"></tiki-list>
Directive
(function(){
'use strict';
angular.module("tiki").directive("tikiList", tikiList)
function tikiList(helper){
var directive = {
restrict:"EA",
scope:{
list: "=", //the object to repeat over, this contains 2 array's
retunObject: "=", //the array that is outputted
selected: "=", //preselected values
max: "=", //maximum range, other elements are greyed out, starts at 0
title:"#title", //the title of this list
type:"#type", //[single, multi]
},
templateUrl:"js/directive/list.html",
link:link,
bindToController: true,
controllerAs:"vm",
controller:controller
}
return directive
function link(scope, el, attr, ctrl){
scope.vm.onClick = onClick
// preprocess the "list" if there is a "selected" attribute
// the "selected" attribute is an object that contains the selected items
// return a "selectedItems" array containing the indeces of matching display names
// add the .active property to the "list" object to the correct indeces
if(scope.vm.selected){
var selectedItems = helper.isItemInList(helper.createArrayFromProperty(scope.vm.selected, "display"), helper.createArrayFromProperty(scope.vm.list, "display"))
for(var i = 0; i < selectedItems.length; i++){
scope.vm.list[selectedItems[i]].active = true
}
}
// add the property .disabled to the "list" if there is a max attribute
// the property will be added to all indeces that exceed the max value
if(scope.vm.max){
for(var y = 0; y < scope.vm.list.length; y++){
if(y >= scope.vm.max){
scope.vm.list[y].disabled = true
}
}
}
function onClick(index){
// only allow items that are in range of the "max" attribute are allowed to be clicked
if(!scope.vm.max || index < scope.vm.max){
if(scope.vm.type === "single"){
angular.forEach(scope.vm.list, function(val, key){
scope.vm.list[key].active = false
})
scope.vm.list[index].active = true
}
if(scope.vm.type === "multi"){
scope.vm.list[index].active = !scope.vm.list[index].active
}
}
}
scope.vm.listing = scope.vm.list
}
}
controller.$inject = [];
function controller(){
}
})()
Directive template
<ul class="listOptions">
<li class="listOptions-title" ng-class="{'show':title}">{{vm.title}}</li>
<li ng-click="vm.onClick($index)" ng-class="{'active':list.active, 'disabled':list.disabled}" ng-repeat="list in vm.listing track by $index">{{list.display}}</li>
</ul>
I think it has something to do with controllerAs but I can't wrap my head around it.
thx,
I think the reason is that the Array is a reference type. When you change the data in service or at async step, the data point goes to a new location in memory, but the data in directive or controller doesn't change.
Instead of writing your function like this:
$timeout(function(){
vm.listObjectSelected = [{"display":"display1", "value":"value1"}, {"display":"display3", "value":"value3"}]
}, 2000)
You should try doing it this way:
$(timeout(function(){vm.listObjectSelected.push({you need data here})},200)
or you can use a promise ,you can return a promise and get it in directive ,use
promise.then(function(){//let data = service.data again}
Hope this can help you.

use filter in angular directive to change collection in ng-options

I have the following directive to set the 10 most used languages in the beginning of a select and set a custom css class:
app.directive('sortTopLocales', function ($timeout) {
// copied from angular-chosen (to get the array of ng-options)
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;
var top10languages = ['en', 'zh', 'es', 'ja', 'pt', 'de', 'ar', 'fr', 'ru', 'ko'];
var top10languagesReversed = top10languages.reverse();
var cssClass = 'top10';
return {
restrict: 'A',
link: function(scope, element, attrs) {
if (attrs.ngOptions) {
var match = attrs.ngOptions.match(NG_OPTIONS_REGEXP);
scope.$watchCollection(match[7], function(locales) {
var alreadySorted = element.find('option.'+cssClass).length;
if(angular.isUndefined(locales) || alreadySorted) { return; }
var top10Index = 0;
$timeout(function() {
element.find('option.'+cssClass).removeClass(cssClass); // reset CSS class
angular.forEach(top10languagesReversed, function(locale) {
var index = locales.indexOf(locale);
if(index !== -1) {
locales.splice(index, 1); // removes it from the array
locales.unshift(locale); // sets it at the beginning of the array
var option = element.find('option[value=' + top10Index + ']');
option.addClass(cssClass);
top10Index++;
}
});
console.log(scope.$eval(match[7])[0], locales[0]);
// without filter => 'en', 'en'
// with filter => 'aa', 'en'
});
});
}
}
};
});
It works as expected without filter, but when I use a filter in ng-options it doesn't:
working:
ng-options="locale for locale in locales" // match[7] = 'locales'
not working:
ng-options="locale for locale in locales | orderBy" // match[7] = 'locales | orderBy'
There is a different output of the changed array and the expression of $watchCollection.
scope.$eval(match[7])[0] // 'aa'
locales[0] // 'en'
When I use the filter inside the directive code and not in the HTML the array isn't updated either.
So basically my question is: How do I update an array with filter changed in $watchCollection to use it in ng-options?

select box : display text 'error' if value not exist in array

I have a key value pair defined as below, which is being used for select using ng-options
$scope.BucketEnum = [
{ display: 'Error', value: 0 },
{ display: '1', value: 1 },
{ display: '2', value: 2 },
{ display: '3', value: 3 },
{ display: '4', value: 4 },
{ display: '5', value: 5 },
{ display: 'Flows', value: 125 },
{ display: 'Recovery', value: 151 }
];
I am using this key value pair to display select box in ng-options
<select ng-model="selectedBucket" ng-options="row.value as rows.display for row in BucketEnum" multiple="multiple" ></select>
now if I set ng-model i.e. $scope.selectedBucket = 10, I want to display the text Error. Is it possible to show value Error for all the values which are not there in $scope.BucketEnum array.
NOTE
I am looking at a more generic way to do this e.g a filter for doing this
SCENARIO
There is certain historical data in database, which has some garbage and some good data.
For each garbage value, i need to show the current garbage value as well as the valid values to select from, so for the end users to fix it.
Would this fit your needs ?
jsfiddle
app.filter('bootstrapValues', function(){
return function(initial, baseBucket){
var result = [];
for(var i=0; i<initial.length; i++){
var flag = false;
for(var j=1; j<baseBucket.length; j++){ //from 1 or 0.. you call
if(initial[i] === baseBucket[j].value){
flag = true;
result.push(baseBucket[j]);
break; // if there are repeated elements
}
}
if(!flag)
result.push(baseBucket[0])
}
return result;
};
});
Using it to start the selectedBucket, in your controller:
// setting initials
$scope.selectedBucket = $filter('bootstrapValues')(initialSet, $scope.bucketEnum);
Does it help?
Edit: Here is other jsfiddle with little modifications, if the value is not in the bucket it add the element to the list with Error display and as a selected value.
Using ng-options generates multiple HTML <select> elements for each item in your BucketEnum array and 'returns' the selected value in your ng-model variable: selectedBucket. I think the only way to display the options without an additional blank entry is to ensure the value of selectedBucket is a valid entry in BucketEnum.
Your question states:
if I set ng-model i.e. $scope.selectedBucket = 10, I want to display
the text Error.
I assume you want to display the value: {{BucketEnum[selectedBucket].display}}
So... starting with $scope.selectedBucket = 10, we want some generic way of implementing a select using ng-options which will reset this value to a default.
You could do this by implementing an attribute directive, allowing you to write:
<select ng-model="selectedBucket" select-default="BucketEnum"
ng-options="row.value as row.display for row in BucketEnum"
multiple="multiple">
An example of this approach is shown below. Note that this assumes the default value is zero and does not handle multiple selections (you'd have to iterate over the selections when comparing to each item in BucketEnum and decide what to do if there is a mix of valid and invalid selections).
app.directive("selectDefault",function(){
return{
restrict: 'A',
scope: false,
link:function(scope,element,attrs){
var arr= scope[attrs.selectDefault]; // array from attribute
scope.$watch(attrs.ngModel,function(){
var i, ok=false;
var sel= scope[attrs.ngModel]; // ng-model variable
for( i=0; i<arr.length; i++){ // variable in array ?
if( arr[i].value == sel ) // nasty '==' only for demo
ok= true;
}
if( ! ok )
scope[attrs.ngModel]=0; // set selectedBucket to 0
});
}
};
});
I've run up a jsfiddle of this here
The downside of this is that I've used a $watch on the ng-model which causes side-effects, i.e. any assignment of the named variable will trigger the $watch function.
If this is the sort of solution you were looking for, you could expand the directive in all sorts of ways, for example:
<select ng-model="selectResult"
select-default="99" array="BucketEnum" initial="selectedBucket"
ng-options="row.value as row.display for row in BucketEnum"
multiple="multiple">
...the idea being that the select-default directive would read the default value ("99" here), the array and an initial value then set selectResult accordingly
You would need to code for this explicitly. Scan the choices you want to set against the choices that are present. If you don't find it, select the Error value too.
Note also that you need to pass an array for selectedBucket and it needs to include the actual option objects not just the values inside them.
<div ng-app="myApp">
<div ng-controller="myController">
<p>Select something</p>
<select ng-model="selectedBucket"
ng-options="row as row.display for row in bucketEnum" multiple="multiple">
</select>
</div>
</div>
.
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
var initialSet = [1, 5, 10];
$scope.bucketEnum = [
{ display: 'Error', value: 0 },
{ display: '1', value: 1 },
{ display: '2', value: 2 },
{ display: '3', value: 3 },
{ display: '4', value: 4 },
{ display: '5', value: 5 },
{ display: 'Flows', value: 125 },
{ display: 'Recovery', value: 151 }
];
var selected = [];
var error = $scope.bucketEnum[0];
angular.forEach(initialSet, function(item) {
var found;
angular.forEach($scope.bucketEnum, function (e) {
if (+item == +e.value) {
console.log('Found ', e);
found = item;
selected.push(e);
}
});
if (typeof found === 'undefined') {
selected.push(error);
}
$scope.selectedBucket = selected;
console.log(selected);
});
});

Resources