override get method in Alloy model - backbone.js

i'm trying to override get: calls in Alloy model, very similar to Backbone, i wrote this but doesn't work
extendModel: function(Model) {
_.extend(Model.prototype, {
// extended functions and properties go here
get: function (attr) {
if (attr=='image')
{
return Ti.Utils.base64decode(this['image'])
}
return this[attr];
}
});
return Model;
},

Here is how i am overriding the set and add methods hope it helps you:
exports.definition = {
config: {
adapter: {
type: "properties",
collection_name: "careCenter",
idAttribute : "CareCenterID"
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {
idAttribute : "CareCenterID"
// extended functions and properties go here
});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
add : function(attrs, opts){
var isDuplicated = false;
if(attrs && attrs.get){
isDuplicated = this.any(function(model){
return model.get("CareCenterID") === attrs.get("CareCenterID");
});
}
if(isDuplicated){
return false;
} else {
Backbone.Collection.prototype.add.call(this, attrs, opts);
}
},
comparator : function(model){
return -model.get("state");
}
});
return Collection;
}
}
extendModel: function(Model) {
_.extend(Model.prototype, {
idAttribute : "RecipientID",
set : function(attrs, opts){
if(attrs.Age != null){
var age = attrs.Age;
var result = "";
if(age <= Alloy.CFG.INFANT){
result = "infant";
} else if(age <= Alloy.CFG.CHILD){
result = "child";
} else if(age <= Alloy.CFG.TEENAGER){
result = "teenager";
} else {
result = "adult";
}
attrs.Group = result;
}
return Backbone.Model.prototype.set.call(this, attrs, opts);
}
});
return Model;
},

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

Filter for nested objects to return all children elements

I have a filter that is on ng-repeat and compares strings of all objects (including nested ones) to a search string. If the search string is found in the object, it returns true.
I'm looking for a way to extend this functionality so that when the search string matches with a string in the object, the filter will return true for that object and will return true for all nested objects in the matching object (this is a tree view, I'm searching for a node and want to show all children nodes when matched).
How would I do that?
My filter looks like this:
.filter('deepFilter', function ($filter) {
return function(text) {
return function (value) {
if(text && text.length > 0) {
var searchTerm = text;
if (angular.isObject(value)) {
var found = false;
angular.forEach(value, function(v) {
found = found || $filter('deepFilter')(searchTerm)(v);
});
return found;
} else if (angular.isString(value)) {
if (value.indexOf(searchTerm) !== -1) {
return true;
} else {
return false;
}
}
} else {
return true;
}
};
};
});
The solution I found is by using a function in the isString part of the filter, and iterating over the collection. If I find the object, I look for it's children using a recursive function and set a visibleAsAChild property for these. Then, I've added a condition in the isObject evaluation to return true for these object that have visibleAsAChild prop.
I'm not sure if this is the most efficient way to do it, but it certainly works.
.filter('deepFilter', function ($filter) {
var currentObject;
var setChildrenToVisible = function(node) {
angular.forEach(node.nodes, function(node) {
if(node.nodes) {
setChildrenToVisible(node);
}
node.visibleAsAChild = true;
});
};
var lookupChildren = function(o, value) {
// console.log(o);
angular.forEach(o.nodes, function(node) {
if (node.name === value) {
setChildrenToVisible(node);
}
});
};
return function(text) {
return function (value) {
if(text && text.length > 0) {
var searchTerm = text;
if (angular.isObject(value)) {
var found = false;
angular.forEach(value, function(v) {
found = found || $filter('deepFilter')(searchTerm)(v);
});
if(found && value.hasOwnProperty('id')) {
currentObject = value;
}
if(value.hasOwnProperty('id') && value.visibleAsAChild) {
return true;
}
return found;
} else if (angular.isString(value)) {
if (value.indexOf(searchTerm) !== -1) {
if(currentObject){
lookupChildren(currentObject, value);
}
return true;
} else {
return false;
}
}
} else {
return true;
}
};
};

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

Extjs paginated grid is not filtering from first page without store,loadPage(1)

I am using ExtJs 4 version and I have created client side pagination. when I search the grid in the second page then it is not searching the store from the first page of grid. this is view.js
{
xtype: 'textfield',
id: 'detailsSearchBox',
selectOnFocus: true,
margin:'0 0 0 10',
mode: 'remote',
listeners: {
change :function(field, newValue, oldValue, options){
Store.clearFilter(true);
var grid = Ext.getCmp('Grid');
var total = localStorage.getItem('total');
var matcher = new RegExp(Ext.String.escapeRegex(newValue), "i");
console.log('grid.store.getCurrentPage ', grid.store.currentPage);
var tb = Ext.getCmp('pagingtool');
console.log('window.data ', window.data);
records = [];
Ext.each(tempClusterData, function(record) {
for (var i = 0; i < grid.columns.length; i++) {
if (grid.omitColumns) {
if (grid.omitColumns.indexOf(grid.columns[i].dataIndex) === -1) {
if (matcher.test(record[grid.columns[i].dataIndex]){
if (!grid.filterHidden && grid.columns[i].isHidden()) {
continue;
} else {
records.push(record);
break;
};
};
};
} else {
if (matcher.test(record[grid.columns[i].dataIndex]) {
//console.log('else - if**** ', record[grid.columns[i].dataIndex], matcher);
if (!grid.filterHidden && grid.columns[i].isHidden()) {
continue;
} else {
records.push(record);
window.data= records;
break;
};
};
};
}
});
data = window.data;
data.length=window.data.length;
grid.store.load({ params: { query: matcher, start: 0, limit: window.total } });
},
}
}
and my store.js is
var fetchedData = function(){
this.data = null;
this.total = 0;
}
var data = JSON.parse(localStorage.getItem('data'));
var total = localStorage.getItem('total');
function createPagination(page, count) {
var tmp = [];
var startIndex = (page * count) - count;
if(startIndex <= data.length-1)
{
var endIndex = startIndex + (count -1);
if(endIndex > data.length -1)
endIndex = data.length -1;
for(;startIndex <= endIndex;startIndex++)
{
tmp.push([data[startIndex].Id, data[startIndex].Name, data[startIndex].start, data[startIndex].end, data[startIndex].status, data[startIndex].year);
}
}
return data;
}
var store = Ext.define('EMS.store.store', {
extend: 'Ext.data.Store',
alias: 'store.ctore',
model: 'EMS.model.cModel',
requires: [
'EMS.model.cModel'
],
//autoLoad: true,
pageSize : 3,
proxy: {
type: 'memory',
reader: {
type: 'array',
root: 'data',
totalProperty : 'total'
}
},
listeners : {
beforeload : function(store, operation, eOpts){
var page = operation.page;
var limit = operation.limit;
fetchedData.data = createPagination(page, limit);
fetchedData.total = total;
store.proxy.data = fetchedData;
}
}
});
I found the reason for my issue here is the code what i have changes in for loop. just see the comment line in else block.
records = [];
Ext.each(cData, function(record) {
for (var i = 0; i < grid.columns.length; i++) {
if (grid.omitColumns) {
if (grid.omitColumns.indexOf(grid.columns[i].dataIndex) === -1) {
if (matcher.test(record[grid.columns[i].dataIndex]){
if (!grid.filterHidden && grid.columns[i].isHidden()) {
continue;
} else {
records.push(record);
break;
};
};
};
} else {
if (matcher.test(record[grid.columns[i].dataIndex]){
if (!grid.filterHidden && grid.columns[i].isHidden()) {
continue;
} else {
records.push(record);
break;
};
}else{
window.data.pop(); // I have added this line so it is working now
}
};
}
});
window.data= records;
window.data.length= records.length;
grid.store.load({ params: {start: 0, limit: window.total } });
If you want to change the paging tool bar display message as per the number of matched records available in the store.
records = [];
var pbt = Ext.getCmp('pagingtoolbar');
Ext.each(cData, function(record) {
for (var i = 0; i < grid.columns.length; i++) {
if (grid.omitColumns) {
if (grid.omitColumns.indexOf(grid.columns[i].dataIndex) === -1) {
if (matcher.test(record[grid.columns[i].dataIndex]){
if (!grid.filterHidden && grid.columns[i].isHidden()) {
continue;
} else {
records.push(record);
break;
};
};
};
} else {
if (matcher.test(record[grid.columns[i].dataIndex]){
if (!grid.filterHidden && grid.columns[i].isHidden()) {
continue;
} else {
records.push(record);
if(records .length < 3){
window.total = records .length;
pbt.child('#first').hide();
pbt.child('#prev').hide();
pbt.child('#next').hide();
pbt.child('#last').hide();
pbt.child('#afterTextItem').hide();
pbt.child('#inputItem').hide();
}else{
window.total = records .length;
pbt.child('#first').show();
pbt.child('#prev').show();
pbt.child('#next').show();
pbt.child('#last').show();
pbt.child('#afterTextItem').show();
pbt.child('#inputItem').show();
}
break;
};
}else{
window.data.pop(); // I have added this line so it is working now
}
};
}
});
window.data= records;
window.data.length= records.length;
grid.store.load({ params: {start: 0, limit: window.total } });

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