AngularJS Property Bag - angularjs

Is there a formal property bag for Angular? I need a way to temporarily store variables and optionally save them between apps.

To save time, I whipped one up. It's here if anyone needs it, however I would love to find one in a framework that is supported by a team:
/**
* Created by Fred Lackey on 4/24/15.
*/
(function (module) {
var propertyBag = function (localStorage) {
var items = [];
var getIndex = function (key) {
if (!key || !key.trim || key.trim().length < 1) { return -1; }
if (items.length < 1) { return -1; }
for (var i = 0; i < items.length; i += 1) {
if (items[i].key.toLowerCase() === key.toLowerCase()) { return i; }
}
return -1;
};
var exists = function (key) {
return (getIndex(key) >= 0);
};
var getItem = function (key) {
var index = getIndex(key);
return (index >= 0) ? items[index] : null;
};
var getValue = function (key) {
var index = getIndex(key);
return (index >= 0) ? items[index].value : null;
};
var putItem = function (key, value) {
if (!key || !key.trim || key.trim().length < 1) { return; }
var index = getIndex(key);
if (index >= 0) {
items[index].value = value;
} else {
items.push({ key: key, value: value });
}
};
var removeItem = function (key) {
var index = getIndex(key);
if (index >= 0) { items.splice(index, 1); }
};
var count = function () {
return items.length;
};
var saveBag = function (key) {
if (!key || !key.trim || key.trim().length < 1) { return; }
localStorage.add(key, items);
};
var loadBag = function (key) {
if (!key || !key.trim || key.trim().length < 1) { return; }
var bag = localStorage.get(key);
if (!bag || !bag.length) { return; }
for (var i = 0; i < bag.length; b += 1) {
if (!bag[i].key || !bag[i].key.trim || bag[i].key.trim().length < 1) { continue; }
putItem(bag[i].key, bag[i].value);
}
localStorage.remove(key);
};
return {
getItem: getItem,
exists: exists,
getValue: getValue,
putItem: putItem,
removeItem: removeItem,
count: count,
save: saveBag,
load: loadBag
};
};
module.factory('propertyBag', propertyBag);
})(angular.module('common'));
Here's the local storage code if you don't already have one (referenced in the code above)...
(function (module) {
var localStorage = function ($window) {
var store = $window.localStorage;
var add = function (key, value) {
value = angular.toJson(value);
store.setItem(key, value);
};
var get = function (key) {
var value = store.getItem(key);
if (value) {
value = angular.fromJson(value);
}
return value;
};
var remove = function (key) {
store.removeItem(key);
};
return {
add: add,
get: get,
remove: remove
}
};
module.factory('localStorage', localStorage);
})(angular.module('common'));

Related

Rewriting ng-options with ng-repeat so it works with md-select

I am trying to transform a Bootstrap select field into Angular Material, but am having difficulty getting the code to work with ng-repeat, instead of ng-options. The original code looks like this:
<select name="{{field.name}}" ng-model="fieldValue" ng-model-options="{getterSetter: true}" sn-select-width="auto" ng-disabled="field.isReadonly()" ng-options="c.value as c.label for c in field.choices track by c.value">
The new AngularJS Material html code looks like this:
<md-select name="{{field.name}}" ng-model="fieldValue" ng-disabled="field.isReadonly()">
<md-option ng-value="c.value" ng-repeat="c in field.choices | filter:searchTerm track by c.value">{{c.label}}</md-option>
</md-select>
The selected item from md-select won't save in the back-end table. What am I missing here? I am pretty sure not being able to use ng-options is causing this issue, but how do I fix it with ng-repeat?
UPDATE: The rest of the out of the box directive code is below for reference.
link: function(scope, element, attrs, ngModel) {
scope.clearSearchTerm = function() {
scope.searchTerm = '';
};
var g_form = scope.getGlideForm();
var field = scope.field;
var fieldOptions;
var isOpen = false;
scope.fieldValue = function() {
return field.value;
};
g_form.$private.events.on('change', function(fieldName, oldValue, newValue) {
if (fieldName == field.name) {} else if (fieldName == field.dependentField) {
field.dependentValue = newValue;
refreshChoiceList();
} else if (typeof field.variable_name !== 'undefined' && field.reference_qual && isRefQualElement(fieldName)) {
refreshReferenceChoices();
}
});
function isRefQualElement(fieldName) {
var refQualElements = [];
if (field.attributes && field.attributes.indexOf('ref_qual_elements') > -1) {
var attributes = spUtil.parseAttributes(field.attributes);
refQualElements = attributes['ref_qual_elements'].split(',');
}
return field.reference_qual.indexOf(fieldName) != -1 || refQualElements.indexOf(fieldName) != -1;
}
function refreshChoiceList() {
var params = {};
params.table = g_form.getTableName();
params.field = field.name;
params.sysparm_dependent_value = field.dependentValue;
var url = urlTools.getURL('choice_list_data', params);
return $http.get(url).success(function(data) {
field.choices = [];
angular.forEach(data.items, function(item) {
field.choices.push(item);
});
selectValueOrNone();
});
}
function selectValueOrNone() {
var hasSelectedValue = false;
angular.forEach(field.choices, function(c) {
if (field.value == c.value)
hasSelectedValue = true;
});
if (!hasSelectedValue && field.choices.length > 0) {
g_form.setValue(field.name, field.choices[0].value, field.choices[0].label);
}
}
function refreshReferenceChoices() {
var params = [];
params['qualifier'] = field.reference_qual;
params['table'] = field.lookup_table;
params['sysparm_include_variables'] = true;
params['variable_ids'] = field.sys_id;
var getFieldSequence = g_form.$private.options('getFieldSequence');
if (getFieldSequence) {
params['variable_sequence1'] = getFieldSequence();
}
var itemSysId = g_form.$private.options('itemSysId');
params['sysparm_id'] = itemSysId;
var getFieldParams = g_form.$private.options('getFieldParams');
if (getFieldParams) {
angular.extend(params, getFieldParams());
}
var url = urlTools.getURL('sp_ref_list_data', params);
return $http.get(url).success(function(data) {
field.choices = [];
angular.forEach(data.items, function(item) {
item.label = item.$$displayValue;
item.value = item.sys_id;
field.choices.push(item);
});
selectValueOrNone();
});
}
var pcTimeout;
g_form.$private.events.on('propertyChange', function(type, fieldName, propertyName) {
if (fieldName != field.name)
return;
if (propertyName == "optionStack") {
$timeout.cancel(pcTimeout);
pcTimeout = $timeout(function() {
field.choices = applyOptionStack(fieldOptions, field.optionStack);
selectValueOrNone();
}, 35);
}
});
setDefaultOptions();
if (field.choices) {
setChoiceOptions(field.choices);
}
selectValueOrNone();
function setDefaultOptions() {
setChoiceOptions([{
value: scope.field.value,
label: scope.field.displayValue || scope.field.placeholder
}]);
}
function setChoiceOptions(options) {
if (options) {
options.forEach(function(option) {
option.value = String(option.value);
});
}
fieldOptions = options;
scope.options = applyOptionStack(options, scope.field.optionStack);
}
function applyOptionStack(options, optionStack) {
if (!optionStack || optionStack.length == 0) {
return options;
}
var newOptions = angular.copy(options);
if (!newOptions) {
newOptions = [];
}
optionStack.forEach(function(item) {
switch (item.operation) {
case 'add':
for (var o in newOptions) {
if (newOptions[o].label == item.label)
return;
}
var newOption = {
label: item.label,
value: item.value
};
if (typeof item.index === 'undefined') {
newOptions.push(newOption);
} else {
newOptions.splice(item.index, 0, newOption);
}
break;
case 'remove':
var itemValue = String(item.value);
for (var i = 0, iM = newOptions.length; i < iM; i++) {
var optionValue = String(newOptions[i].value);
if (optionValue !== itemValue) {
continue;
}
newOptions.splice(i, 1);
break;
}
break;
case 'clear':
newOptions = [];
break;
default:
}
});
return newOptions;
}
}
};
}

how to apply filter functionality on a particular trees node rather on the whole tree

I am searching for compName-1 in the search box. It is returning all the repos in which this compName-1 string is found.
I dont want the search option to search in all the repos. I want it to search in particular repo which is checked. Is there any way to achieve it?
Please find my problem in this plunker. https://plnkr.co/edit/iCOiJDjeP8httwnf2c0t?p=preview
Controller.js
var app = angular.module('testApp', []);
app.controller('treeTable', ['$scope', '$http', function ($scope, $http) {
$scope.list = [];
$scope.list.push({name: "repo 1", version:"0.0", size:"0", description:" ", label: "", date: "XXX", id:"repo1"});
$scope.list.push({name: "repo 8", version:"0.0", size:"0", description:" ", label: "", date: "XXX", id:"repo2"});
$scope.list.push({name: "repo 7", version:"0.0", size:"0", description:" ", label: "", date: "XXX", id:"repo3"});
$scope.displayChildren = function(item, id) {
console.log(item.showTree);
item.showTree = !item.showTree;
console.log(id);
if(id === 'repo'+1) {
if(!(item.children && item.children.length > 0)) {
$http.get("List_repo1.json")
.then(function(response) {
console.log('response repo1');
item.children = response.data.response.repoBundles;
item.showTree = true;
});
}
}
if(id === 'repo'+2) {
if(!(item.children && item.children.length > 0)) {
$http.get("List_repo2.json")
.then(function(response) {
console.log('response repo2');
item.children = response.data.response.repoBundles;
item.showTree = true;
});
}
}
if(id === 'repo'+3) {
if(!(item.children && item.children.length > 0)) {
$http.get("List_repo3.json")
.then(function(response) {
console.log('response repo3');
item.children = response.data.response.repoBundles;
item.showTree = true;
});
}
}
};
$scope.toggleChildren = function(item, parentItem) {
console.log(parentItem);
if(parentItem !== void 0) {
if(parentItem.bundles !== void 0) {
$scope.$emit('changeBundles', parentItem);
} else if(parentItem.item.children !== void 0) {
console.log('parent child');
$scope.$emit('changeParent', parentItem);
}
}
if (item.children !== void 0) {
console.log(item.children);
console.log('inside children');
$scope.$broadcast('changeChildren', item);
} else if(item.components !== void 0){
console.log(item + 'inside comp');
$scope.$broadcast('changeComponents', item);
}
};
$scope.toggleAllCheckboxes = function ($event) {
var i, item, len, ref, results, selected;
selected = $event.target.checked;
if(selected) {
$scope.selectedCheckbox = false;
} else {
$scope.selectedCheckbox = true;
}
ref = $scope.list;
results = [];
for (i = 0, len = ref.length; i < len; i++) {
item = ref[i];
item.selected = selected;
if (item.children != null) {
results.push($scope.$broadcast('changeChildren', item));
} else {
results.push(void 0);
}
}
return results;
};
$scope.$on('changeChildren', function (event, parentItem) {
var child, i, len, ref, results;
ref = parentItem.children;
results = [];
console.log(ref === void 0);
for (i = 0, len = ref.length; i < len; i++) {
child = ref[i];
child.selected = parentItem.selected;
console.log("child" + child);
if (child.components != null) {
console.log("inside if " + child.components);
results.push($scope.$broadcast('changeComponents', child));
} else {
results.push(void 0);
}
}
return results;
});
$scope.$on('changeComponents', function (event, parentItem) {
var child, i, len, ref, results;
ref = parentItem.components;
results = [];
console.log(parentItem.selected);
for (i = 0, len = ref.length; i < len; i++) {
child = ref[i];
child.selected = parentItem.selected;
console.log("child" + child.selected + child.value);
}
});
$scope.$on('changeParent', function (event, parentScope) {
var children;
children = parentScope.item.children;
parentScope.item.selected = $filter('selected')(children).length === children.length;
parentScope = parentScope.$parent.$parent;
if (parentScope.item != null) {
return $scope.$broadcast('changeParent', parentScope);
}
});
$scope.$on('changeBundles', function (event, parentScope) {
var children;
children = parentScope.bundles.components;
parentScope.bundles.selected = $filter('selected')(children).length === children.length;
parentScope = parentScope.$parent.$parent;
if (parentScope.item !== null) {
return $scope.$broadcast('changeParent', parentScope);
}
});
}]);
app.filter('selected', [
'$filter',
function ($filter) {
return function (files) {
return $filter('filter')(files, { selected: true });
};
}
]);

How do I optimize filtering in AngularJS?

I have here a filter for AngularJS. Is there any better way for this?
return function (data, selected) {
var result = [];
if (selected[0] == 'All Types') {
result = data;
}
else {
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < selected.length; j++) {
if (data[i].Type == selected[j]) {
result.push(data[i]);
}
}
}
}
return result;
};
return function (data, selected) {
var result = [];
if (selected[0] == 'All Types') {
result = data;
}
else {
result=data.filter(x=>selected.indexOf(x.Type)>-1);
}
return result;
};
Obviously this is using lodash, but native methods can be substituted:
return (data, selected) => {
return
_.first(selected) === 'All Types' ? data :
_.reduce(data, (sum, value, key) => {
if (_.includes(selected, value.Type)) {
sum.push(value);
}
return sum;
}, []);
}
Use data for selected value. I am assuming selected value is in data[i].selected.
return function (data) {
var result = [];
if (data[0].selected == 'All Types') {
result = data;
}
else {
for (var i = 0; i < data.length; i++) {
if (data[i].Type == data[i].selected) {
result.push(data[i]);
}
}
}
return result;
};
We can use filters
return function (data) {
if (data[0].selected == 'All Types') {
return data;
}
else {
return data.filter(function (item) {
return item.Type == item.selected;
});
}
};

Unique after filter Angular

So I've been trying to figure this out for a while but it's still stumping me.
Ultimately I want to be able to do the following
object in objects | unique:'DateTime | limitTo:4'//As the year is the first 4 characters
(oh well y10k :))
But I don't know how I'd write that, I'd prefer to have it be inline html code so I don't need to mess around with dependency injection.
Thanks in advance!
Use this derictive:
.filter('unique', function () {
return function (items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {}, newItems = [];
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function (item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
items = newItems;
}
return items;
};
});

How do I watch every object in a collection?

In an Angular scope, I have a collection of objects that carry some data, plus x and y coordinates. Some other scope variables must be recomputed based on the x and y values. What is the best way to do it efficiently?
If I use $scope.$watch(collection, handler) or $scope.$watchCollection(collection, handler), I don't get notified about changes to the objects it contains.
If I use $scope.$watch(collection, handler, true), I do get notified, but when anything changes, not only x and y. Plus, I don't know which element of the collection was changed (and I imagine that this deep comparison is rather costly).
Ideally, I would like to write something like $scope.$watchObjects(collection, ["x", "y"], handler), where my handler would be called with the changed object and possibly its index. Is there an easy way to do that?
Could you do:
angular.forEach(colletion, function(object) {
$scope.$watch(object, function() {
... I'm not sure what would you like to do with object here...
})
}, true)
I am pretty sure it was in this video: https://www.youtube.com/watch?v=zyYpHIOrk_Y
but somewhere I saw Angular devs talking about mapping the data you are watching to a smaller subset, something like this maybe:
$scope.$watchCollection(function() {
return yourList.map(function(listItem) {
return { 'x': listItem.x, 'y': listItem.y };
};
}, function(newVal, oldVal) {
// perform calculations
});
That would leave you $watching just an array of objects having x and y properties.
$scope.$watch('collection', function() {
...
}, true);
Keep in mind that the collection must be declared on the $scope.
Based on Slaven Tomac’s answer, here's what I came up with. Basically: this uses a $watchCollection to detect when items are inserted or added on the collection. For each added item, it starts monitoring it. For each removed item, it stops monitoring it. It then informs a listener each time an object changes.
This further allows to refine what should be considered as a change in the object itself or a change in the collection only. The sameId function is used to test whether two objects a and b should be considered to be the same (it could just a === b, but it could be something more sophisticated — in particular, if you pass in a field name as the sameId argument [e.g., "id"], then two objects will be considered to be “the same.”)
The createArrayDiffs is adapted from a similar change-detection method in the Eclipse Modeling Framework and is interesting in its own right: it returns a list of changes that happened between an array and another array. Those changes are insertions, removals, and object changes (according to the passed fields).
Sample usage:
watchObjectsIn($rootScope, "activities", "id", ["x", "y"], function (oldValue, newValue) {
console.log("Value of an object changed: from ", oldValue, " to ", newValue);
});
Of course, I'm interested in any simpler and/or more efficient solution!
Implementation (compiled TypeScript):
function watchObjectsIn(scope, expr, idField, watchedFields, listener) {
var fieldCompareFunction = makeFieldCompareFunction(watchedFields);
var unbindFunctions = [];
function doWatch(elem, i) {
var unbindFunction = scope.$watch(function () {
return elem;
}, function (newValue, oldValue) {
if (newValue === oldValue)
return;
if (!fieldCompareFunction(oldValue, newValue))
listener(oldValue, newValue);
}, true);
unbindFunctions.push(unbindFunction);
}
function unwatch(elem, i) {
unbindFunctions[i]();
unbindFunctions.splice(i, 1);
}
scope.$watchCollection(expr, function (newArray, oldArray) {
if (isUndef(newArray))
return;
var diffs = createArrayDiffs(oldArray, newArray, idField, fieldCompareFunction);
if (diffs.length === 0 && newArray.length !== unbindFunctions.length) {
for (var i = unbindFunctions.length - 1; i >= 0; i--) {
unwatch(null, 0);
}
diffs = createArrayDiffs([], newArray, idField);
}
_.forEach(diffs, function (diff) {
switch (diff.changeType()) {
case 0 /* Addition */:
doWatch(diff.newValue, diff.position);
break;
case 1 /* Removal */:
unwatch(diff.oldValue, diff.position);
break;
case 2 /* Change */:
listener(diff.oldValue, diff.newValue);
break;
}
});
});
}
function isUndef(v) {
return typeof v === "undefined";
}
function isDef(v) {
return typeof v !== "undefined";
}
function parseIntWithDefault(str, deflt) {
if (typeof deflt === "undefined") { deflt = 0; }
var res = parseInt(str, 10);
return isNaN(res) ? deflt : res;
}
function cssIntOr0(query, cssProp) {
return parseIntWithDefault(query.css(cssProp));
}
function randomStringId() {
return Math.random().toString(36).substr(2, 9);
}
var ArrayDiffChangeType;
(function (ArrayDiffChangeType) {
ArrayDiffChangeType[ArrayDiffChangeType["Addition"] = 0] = "Addition";
ArrayDiffChangeType[ArrayDiffChangeType["Removal"] = 1] = "Removal";
ArrayDiffChangeType[ArrayDiffChangeType["Change"] = 2] = "Change";
})(ArrayDiffChangeType || (ArrayDiffChangeType = {}));
var ArrayDiffEntry = (function () {
function ArrayDiffEntry(position, oldValue, newValue) {
this.position = position;
this.oldValue = oldValue;
this.newValue = newValue;
}
ArrayDiffEntry.prototype.changeType = function () {
if (isUndef(this.oldValue))
return 0 /* Addition */;
if (isUndef(this.newValue))
return 1 /* Removal */;
return 2 /* Change */;
};
return ArrayDiffEntry;
})();
function makeFieldCompareFunction(fields) {
return function (o1, o2) {
for (var i = 0; i < fields.length; i++) {
var fieldName = fields[i];
if (o1[fieldName] !== o2[fieldName])
return false;
}
return true;
};
}
function createArrayDiffs(oldArray, newArray, sameId, sameData, undefined) {
if (isUndef(sameId)) {
sameId = angular.equals;
} else if (_.isString(sameId)) {
var idFieldName = sameId;
sameId = function (o1, o2) {
return o1[idFieldName] === o2[idFieldName];
};
}
var doDataChangedCheck = isDef(sameData);
if (doDataChangedCheck && !_.isFunction(sameData)) {
if (_.isString(sameData))
sameData = [sameData];
var fieldsToCheck = sameData;
sameData = makeFieldCompareFunction(fieldsToCheck);
}
var arrayDiffs = [];
function arrayIndexOf(array, element, index) {
for (var i = index; i < array.length; i++) {
if (sameId(array[i], element))
return i;
}
return -1;
}
var oldArrayCopy = oldArray ? oldArray.slice() : [];
var index = 0;
var i;
for (i = 0; i < newArray.length; i++) {
var newValue = newArray[i];
if (oldArrayCopy.length <= index) {
arrayDiffs.push(new ArrayDiffEntry(index, undefined, newValue));
} else {
var done;
do {
done = true;
var oldValue = oldArrayCopy[index];
if (!sameId(oldValue, newValue)) {
var oldIndexOfNewValue = arrayIndexOf(oldArrayCopy, newValue, index);
if (oldIndexOfNewValue !== -1) {
var newIndexOfOldValue = arrayIndexOf(newArray, oldValue, index);
if (newIndexOfOldValue === -1) {
arrayDiffs.push(new ArrayDiffEntry(index, oldValue, undefined));
oldArrayCopy.splice(index, 1);
done = false;
} else if (newIndexOfOldValue > oldIndexOfNewValue) {
if (oldArrayCopy.length <= newIndexOfOldValue) {
newIndexOfOldValue = oldArrayCopy.length - 1;
}
arrayDiffs.push(new ArrayDiffEntry(index, oldValue, undefined));
oldArrayCopy.splice(index, 1);
arrayDiffs.push(new ArrayDiffEntry(newIndexOfOldValue, undefined, oldValue));
oldArrayCopy.splice(newIndexOfOldValue, 0, oldValue);
done = false;
} else {
arrayDiffs.push(new ArrayDiffEntry(oldIndexOfNewValue, newValue, undefined));
oldArrayCopy.splice(oldIndexOfNewValue, 1);
arrayDiffs.push(new ArrayDiffEntry(index, undefined, newValue));
oldArrayCopy.splice(index, 0, newValue);
}
} else {
oldArrayCopy.splice(index, 0, newValue);
arrayDiffs.push(new ArrayDiffEntry(index, undefined, newValue));
}
} else {
if (doDataChangedCheck && !sameData(oldValue, newValue)) {
arrayDiffs.push(new ArrayDiffEntry(i, oldValue, newValue));
}
}
} while(!done);
}
index++;
}
for (i = oldArrayCopy.length; i > index;) {
arrayDiffs.push(new ArrayDiffEntry(--i, oldArrayCopy[i], undefined));
}
return arrayDiffs;
}

Resources