How can I link data coming from an API to a column in a data table in Angularjs - angularjs

There is a data table which is getting data from an API.
This API is responsible for populating every column on the data table. I added a new column named "Project Involved" which should get filled by the data "ProjectNames" coming from API.
The problem that am facing is- I am not able to link this data to the column on data table.
There are other columns that are getting populated.
This is the column name:
{
data: 'ProjectsInvolved',
title: $translate.instant('Projects involved'),
width: 80
}
This is where we link the API to the data table:
"use strict";
angular
.module('project')
.controller(
'ProjectPermissionsGridController',
function (
$scope,
roleConstants,
$timeout,
FormHelperService,
$translate,
$mdDialog,
logger,
projectConstants,
DateHelperService,
ResourceService,
ProjectService,
$stateParams,
$filter
) {
var pm = this;
pm.resources = [];
pm.copyOfAllResources = [];
pm.pmFormBtn = $translate.instant('btn.save');
pm.projectMemberList = [];
pm.IsProjectManager = false;
pm.maxAllowedHoursLimit = projectConstants.PROJECT_COST_PER_HOUR;
pm.projectMembersData = {
EmpID: '',
Read: roleConstants.NONE,
Write: roleConstants.NONE,
IsProjectManager: false,
// ProjectNames: pm.projectMemberList.ProjectNames
ProjectNames: ''
};
pm.showNoErrorsPerm = true;
pm.maxDate = DateHelperService.setTimeFormattedObject(new Date());
var currentIndex = '';
pm.isProjectMemberEdit = false;
var oldProjectMemberData = {};
pm.udpateResourcePersmissions = function () {
if (pm.projectMembersData.IsProjectManager) {
pm.projectMembersData.Read = roleConstants.ALL;
pm.projectMembersData.Write = roleConstants.ALL;
}
};
pm.initDataTableConfig = function (column, config, api) {
$scope.dtColumns = column;
$scope.dtConfig = config;
$scope.dtApiProjectMembers = api;
};
// Catching the event and binding the data
$scope.$on('evt.project-member-details', function (e, projectMembers) {
pm.projectMemberList = projectMembers;
pm.bindDataToGrid(false, projectMembers);
});
$scope.$on('evt.resources-list', function (e, resources) {
pm.resources = resources;
pm.copyOfAllResources = resources;
});
// On change to remove the cost per hour required validation.
pm.removeCostperHourValidation = function () {
if (!pm.projectMembersData.CostPerHour) {
pm.projectMembersData.CostPerHour = null;
}
if (
parseFloat(pm.projectMembersData.CostPerHour) >
pm.maxAllowedHoursLimit
) {
pm.projectMembersData.CostPerHour = pm.maxAllowedHoursLimit;
}
};
/**
* Add / update new data to project members grid.
*/
pm.addNewProjectMember = function (valid, $event) {
if (!valid) {
$scope.showNoErrorsPerm = false;
return false;
}
pm.projectMembersData.ProjectMember =
FormHelperService.getSelectedObject(
pm.resources,
'EmpID',
pm.projectMembersData.EmpID,
false
).EmpFirstName +
' ' +
FormHelperService.getSelectedObject(
pm.resources,
'EmpID',
pm.projectMembersData.EmpID,
false
).EmpLastName;
pm.projectMembersData.ReadText = projectConstants.PERMISSION_TYPE_OBJ[
pm.projectMembersData.Read
]
? projectConstants.PERMISSION_TYPE_OBJ[pm.projectMembersData.Read]
: '--';
pm.projectMembersData.WriteText = projectConstants.PERMISSION_TYPE_OBJ[
pm.projectMembersData.Write
]
? projectConstants.PERMISSION_TYPE_OBJ[pm.projectMembersData.Write]
: '--';
// pm.projectMembersData.ProjectsInvolved = 'OSMOSYS';
if ($scope.isProjectEdit) {
membersEditProjectForm();
} else {
memberNewProjectForm($event);
}
};
/**
* Function to be executed in the new project page.
* #param {*} event
*/
function memberNewProjectForm($event) {
// Setting this value at the time of clicking on edit button.
if (pm.isProjectMemberEdit) {
oldProjectMemberData = {};
angular.extend(
pm.projectMemberList[currentIndex],
pm.projectMembersData
);
pm.pmFormBtn = $translate.instant('btn.add');
} else {
pm.projectMembersData.CreatedOn =
DateHelperService.setTimeFormatString(new Date());
pm.projectMemberList.splice(0, 0, pm.projectMembersData);
// remove that element from employees dropdown to not to select it again
enableOrDisableEmployee(pm.projectMembersData.EmpID, true);
}
angular.element($event.target).data('isInvalid', true);
$scope.showNoErrorsPerm = true;
pm.projectMembersData = {
EmpID: '',
Read: roleConstants.NONE,
Write: roleConstants.NONE,
IsProjectManager: false,
ProjectNames: ''
};
pm.bindDataToGrid(!pm.isProjectMemberEdit, pm.projectMemberList);
$scope.updateProjectMembers({ projectMembers: pm.projectMemberList });
pm.isProjectMemberEdit = false;
}
/**
* Function to add a new project member
* #param {*} data
* #param {*} projectMemberDetails
*/
function addProjectMember(data, projectMemberDetails) {
ProjectService.addProjectMember(data).then(function (response) {
if (response.isValid()) {
pm.projectMemberList.splice(0, 0, pm.projectMembersData);
pm.bindDataToGrid(false, pm.projectMemberList);
enableOrDisableEmployee(pm.projectMembersData.EmpID, true);
resetPMForm();
$scope.$emit('evt.update-project-managers', projectMemberDetails);
logger.success('Resource has been added to the project');
} else {
var errorMsg = response.getMessage(
$translate.instant('prj.resource'),
$translate.instant('event.adding')
);
logger.error(errorMsg);
}
});
}
/**
* Function to update a project member's details
* #param {*} data
* #param {*} projectMemberDetails
*/
function updateProjectMember(data, projectMemberDetails) {
ProjectService.updateProjectMember(data).then(function (response) {
if (response.isValid()) {
angular.extend(
pm.projectMemberList[currentIndex],
pm.projectMembersData
);
pm.pmFormBtn = $translate.instant('btn.add');
pm.bindDataToGrid(pm.isProjectMemberEdit, pm.projectMemberList);
enableOrDisableEmployee(pm.projectMembersData.EmpID, true);
$scope.$emit('evt.update-project-managers', projectMemberDetails);
resetPMForm();
pm.isProjectMemberEdit = false;
logger.success("Project member's details have been updated.");
} else {
var errorMsg = response.getMessage(
$translate.instant('prj.project_members_details'),
$translate.instant('event.updating')
);
logger.error(errorMsg);
}
});
}
/**
* Function to be executed in the project details form
*/
function membersEditProjectForm() {
var projectMemberDetails = {
empID: pm.projectMembersData.EmpID,
name: pm.projectMembersData.ProjectMember,
isProjectManager: pm.projectMembersData.IsProjectManager,
};
var members = [];
// Adding the projectCode to project member's data
pm.projectMembersData.ProjectCode = $stateParams.projectID;
// Creating the UserID property as the add and update project members API methods expects the UserID and not
// the EmpID
pm.projectMembersData.UserID = pm.projectMembersData.EmpID;
// Adding the current project memeber to the members array as the API methods for the
// add and update project member expects an array
members.push(pm.projectMembersData);
if (pm.isProjectMemberEdit) {
updateProjectMember(members, projectMemberDetails);
} else {
addProjectMember(members, projectMemberDetails);
}
}
pm.eleFocus = function () {
FormHelperService.searchElementFocus();
};
// Clearing the searched item when closing the dropdown
pm.clearSearchTerm = function () {
pm.projectMemberSearchText = '';
};
/**
* return the search results for auto complete controls.
*/
pm.querySearch = function (
query,
list,
listObjProp,
listObjPropName,
splitObjProp
) {
if (!list || list === '') {
list = [];
}
if (listObjPropName) {
var queryObj = {};
queryObj[listObjPropName] = query;
if (splitObjProp) {
var listOjProp = listObjProp.split('.');
pm[listOjProp[0]][listOjProp[1]] = $filter('filter')(
list,
queryObj
);
} else {
pm[listObjProp] = $filter('filter')(list, queryObj);
}
} else {
pm[listObjProp] = $filter('filter')(list, query);
}
};
// Fetching the row and row index when clicking on edit button and showing the details in form
angular.element('table').on('click', '.edit-project-member', function () {
pm.projectMembersData = $scope.dtApiProjectMembers.getRowData(
this.parentElement
);
pm.IsProjectManager = pm.projectMembersData.IsProjectManager;
if (pm.projectMembersData.EmpCostPerHour) {
pm.projectMembersData.EmpCostPerHour =
typeof pm.projectMembersData.EmpCostPerHour === 'string'
? pm.projectMembersData.EmpCostPerHour
: pm.projectMembersData.EmpCostPerHour.toFixed(2);
}
if (pm.projectMembersData.CostPerHour) {
pm.projectMembersData.CostPerHour =
typeof pm.projectMembersData.CostPerHour === 'string'
? pm.projectMembersData.CostPerHour
: pm.projectMembersData.CostPerHour.toFixed(2);
}
// Adding the code as IsProjectManager property is mandatory and sometimes may not be defined
if (!pm.projectMembersData.IsProjectManager) {
pm.projectMembersData.IsProjectManager = false;
}
angular.copy(pm.projectMembersData, oldProjectMemberData);
pm.isProjectMemberEdit = true;
pm.pmFormBtn = $translate.instant('btn.update');
currentIndex = $scope.dtApiProjectMembers.getRowIndex(
this.parentElement
);
});
// Showing an alert and Deleting the row data
angular
.element('table')
.on('click', '.delete-project-member', function () {
var deleteRowIndex = $scope.dtApiProjectMembers.getRowIndex(
this.parentElement
);
var deleteRowData = $scope.dtApiProjectMembers.getRowData(
this.parentElement
);
showConfirmDialog(deleteRowIndex, deleteRowData);
});
function showConfirmDialog(deleteRowIndex, deleteRowData) {
var confirm = $mdDialog
.confirm({
onComplete: function afterShowAnimation() {
var $dialog = angular.element(
document.querySelector('md-dialog')
);
var $dialogContent = $dialog.find('md-dialog-content');
var $actionsSection = $dialog.find('md-dialog-actions');
var $cancelButton = $actionsSection.children()[0];
var $confirmButton = $actionsSection.children()[1];
angular.element($dialogContent).addClass('custom-confirm-class');
angular.element($confirmButton).addClass('md-raised md-warn');
angular.element($cancelButton).addClass('md-raised');
},
})
.title($translate.instant('prj.confirm_pm_delete'))
.textContent(
'Are you sure you want to delete the selected project member?' +
deleteRowData.ProjectMember
)
.ariaLabel($translate.instant('prj.confirm_pm_delete'))
.ok($translate.instant('bar.delete'))
.cancel($translate.instant('bar.cancel'));
$mdDialog.show(confirm).then(function () {
pm.isProjectMemberEdit = false;
if ($scope.isProjectEdit) {
var projectMemberData = [
{
ProjectCode: $stateParams.projectID,
UserID: deleteRowData.EmpID,
},
];
ProjectService.deleteProjectMember(projectMemberData).then(
function (response) {
if (response.isValid()) {
logger.success(
$translate.instant('prj.project_member_removed_success_msg')
);
deleteProjectMember(deleteRowIndex, deleteRowData);
if (deleteRowData.IsProjectManager) {
$scope.$emit(
'evt.project-manager-removed',
deleteRowData.EmpID.toString()
);
}
} else {
var errorMsg = response.getMessage(
$translate.instant('prj.project_member'),
$translate.instant('event.deleting')
);
logger.error(errorMsg);
}
}
);
} else {
deleteProjectMember(deleteRowIndex, deleteRowData);
$scope.updateProjectMembers({
projectMembers: pm.projectMemberList,
});
}
pm.pmFormBtn = $translate.instant('btn.save');
pm.projectMembersData = {
EmpID: '',
Read: roleConstants.NONE,
Write: roleConstants.NONE,
IsProjectManager: false,
};
});
}
function deleteProjectMember(deleteRowIndex, deleteRowData) {
pm.projectMemberList.splice(deleteRowIndex, 1);
pm.bindDataToGrid(false, pm.projectMemberList);
var oldEmpId = deleteRowData.EmpID;
// Add that element to employees dropdown to be able to select it again
enableOrDisableEmployee(oldEmpId, false);
}
function enableOrDisableEmployee(empId, isEnable) {
for (var j = 0; j < pm.resources.length; j++) {
if (pm.resources[j].EmpID === empId) {
pm.resources[j].isDisabled = isEnable;
return;
}
}
}
pm.isReadPermissionLevelLess = function (level) {
return pm.projectMembersData.Read < level ? true : false;
};
/**
* To change the write permission default value based on the read permission
* Ex : If read - own, then write should not be all
*/
pm.changeWriteDefaultValue = function () {
if (pm.projectMembersData.Read < pm.projectMembersData.Write) {
pm.projectMembersData.Write = pm.projectMembersData.Read;
}
};
/**
* Get the cost per hour of an employee when the employee value is changed
*/
pm.getCostPerHour = function () {
ResourceService.getEmployeeCostPerHour(
pm.projectMembersData.EmpID
).then(
function (response) {
if (response.isValid()) {
var costPerHour = response.getData();
if (costPerHour && costPerHour[0]) {
pm.projectMembersData.EmpCostPerHour =
costPerHour[0].CostPerHour.toFixed(2);
pm.projectMembersData.CostPerHour =
pm.projectMembersData.EmpCostPerHour;
}
}
},
function (error) {
logger.error(error);
}
);
};
$scope.$on("evt.clear-pm-members-grids-data", function () {
pm.projectMemberList = [];
pm.bindDataToGrid(false, pm.projectMemberList);
});
// Binding the data to grid with updated data.
pm.bindDataToGrid = function (reOrder, projectMembers) {
pm.projectMemberList = projectMembers;
if (pm.projectMemberList && pm.projectMemberList.length) {
var currPage = $scope.dtApiProjectMembers.getPageInfo()['page'];
$scope.dtApiProjectMembers.bindData(pm.projectMemberList);
currPage = currPage >= 0 ? currPage : 0;
if (reOrder) {
$scope.dtApiProjectMembers.orderByColumn([[1, 'desc']]);
} else {
$scope.dtApiProjectMembers.setPage(currPage);
}
} else {
$scope.dtApiProjectMembers.bindData([]);
}
};
function resetPMForm() {
$scope.showNoErrorsPerm = true;
$scope.vm.pmData.$setPristine();
$scope.vm.pmData.$setUntouched(true);
pm.projectMembersData = {
EmpID: '',
Read: roleConstants.NONE,
Write: roleConstants.NONE,
IsProjectManager: false,
};
}
$scope.$on('evt.reset-project-members-form', function () {
resetPMForm();
});
}
);
I tried linking data when adding new resource- I can see project getting linked. I hard coded this to see if I was working on the right container.

Related

Angularsjs cannot find service function

My filterProducts function makes a call to findIntersection which is present but I get an error findIntersection is undefined.
angular.module('BrandService', [])
.service('BrandService', function ($filter, DataService) {
var productDb;
var products;
return {
filterProducts(brands, priceRange) {
var filteredProducts = [];
var brandProducts = [];
var priceProducts = [];
var productsArray = [];
var brandChecked = false;
var priceChecked = false;
angular.forEach(brands, function (brand) {
if (brand.checked) {
brandChecked = true;
angular.extend(brandProducts,
$filter('filter')(productDb, { 'brand': brand.name }));
}
if (brandChecked) {
productsArray.push(brandProducts);
console.log('brandProducts = ', brandProducts)
}
});
angular.forEach(priceRange, function (price) {
if (price.checked) {
priceChecked = true;
let filteredProductDb = productDb.filter((prod) => {
return (prod.price >= price.low && prod.price <= price.high);
});
angular.extend(priceProducts, filteredProductDb);
}
});
if (priceChecked) {
productsArray.push(priceProducts);
// console.log('priceProducts = ', priceProducts)
}
if (!brandChecked && !priceChecked) {
filteredProducts = products;
} else {
if (productsArray.length > 1) {
filteredProducts = findIntersection(productsArray);
} else {
filteredProducts = productsArray[0];
}
}
return filteredProducts;
},
findIntersection(productsArray) {
console.log('findIntersection called')
var filteredProducts = [];
var filteredSet = new Set();
for(var i=0; i < productsArray.length - 1; i++) {
var products1 = productsArray[i];
var products2 = productsArray[i+1];
angular.forEach(products1, function(product1) {
angular.forEach(products2, function(product2) {
if(product1._id == product2._id) {
filteredSet.add(product1);
}
});
});
}
filteredProducts = Array.from(filteredSet);
return filteredProducts;
}
}
})
My filterProducts function makes a call to findIntersection which is present but I get an error findIntersection is undefined.
My filterProducts function makes a call to findIntersection which is present but I get an error findIntersection is undefined.
You are returning a javascript object with properties. You are not defining global functions.
You need to store the service returned before :
var service = { findProducts: ... , findIntersection: ... };
return service;
And instead of calling findIntersection, call service.findIntersection.
You have to make a reference to local object.
Simply change this: filteredProducts = findIntersection(productsArray);
to this: filteredProducts = this.findIntersection(productsArray);

Cordova.writefile function doesn't run, but doesn't give an error

I'm making an ionic application. I have a function called scope.win() that's supposed to fire when a quiz is finished, and then update the JSON file to add that quiz to the number of finished quizzes but it doesn't run properly.It doesn't give off any error. The function just stops running at a certain point and the app keeps going and nothing is happening.
This is the controller's file
angular.module('controllers', ['services'])
.controller('MainCtrl', function ($scope, $state, data) {
$scope.$on('$ionicView.enter', function () {
data.create();
});
$scope.chapter = data.chapterProgress();
})
.controller("BtnClick", function ($scope, lives, data, $cordovaFile, $ionicScrollDelegate) {
var live = 3;
var clickedOn = [];
var numQuestions;
$scope.part2Cred = false;
$scope.part3Cred = false;
$scope.part4Cred = false;
$scope.part5Cred = false;
$scope.part6Cred = false;
$scope.part7Cred = false;
$scope.part8Cred = false;
$scope.part9Cred = false;
$scope.part10Cred = false;
$scope.partQCred = false;
$scope.setNumQuestions = function(num){
numQuestions = num;
}
$scope.updatelives = function (){
//grabs the element that is called liv then updates it
var livesOnPage = document.getElementById('liv');
livesOnPage.innerHTML = live;
}
$scope.wrong = function (event){
var selec = document.getElementById(event.target.id);
if(clickedOn.includes(selec)){}
else{
selec.style.color = "grey";
clickedOn.push(selec);
live = live - 1;
if(live == 0){
$scope.gameover();
}
else{
$scope.updatelives();
}
}
}
$scope.right = function (event,chapter, section){
var selec = document.getElementById(event.target.id);
if(clickedOn.includes(selec)){}
else{
selec.style.color = "green";
clickedOn.push(selec);
numQuestions = numQuestions - 1;
if(numQuestions === 0){
$scope.win(chapter, section);
}
}
}
$scope.gameover = function(){
alert("game over please try again");
live = 3;
$ionicScrollDelegate.scrollTop();
$scope.partQCred = false;
$scope.part1Cred = !$scope.part1Cred;
for(i = 0; i< clickedOn.length;i++){
clickedOn[i].style.color = "rgb(68,68,68)";
}
}
$scope.win = function (chapter, section) {
alert("Well Done");
var data = data.chapterProgress(); // It is at this point that the //function stops running without giving off any error.
alert("Good Job");
var sectionsComplete = data[chapter].sectionsCompleted;
var totalsection = data[chapter].totalSections;
if (section === totalSection) {
window.location.href = "#/chapter1sections";
return;
}
if (section > sectionsComplete) {
data[chapter].sectionsCompleted += 1;
var url = "";
if (ionic.Platform.isAndroid()) {
url = "/android_asset/www/";
}
document.addEventListener('deviceready', function () {
$cordovaFile.writeFile(url + "js/", "chapters.json", data, true)
.then(function (success) {
// success
alert("Json file updated")
window.location.href = "#/chapter1sections";
}, function (error) {
// error
});
});
}
}
});
Here is the Services file. It seems to run fine but it is referenced a lot in the problematic sections of code in the controllers so I figured it was necessary to put it here.
angular.module('services', [])
.service('lives', function () {
var live = 3;
return {
getlives: function () {
return live;
},
setlives: function (value) {
live = value;
}
};
})
.service('data', function ($cordovaFile, $http) {
var url = "";
if (ionic.Platform.isAndroid()) {
url = "/android_asset/www/";
}
return {
//Run this function on startup to check if the chapters.json file exists, if not, then it will be created
create: function () {
var init = {
"one": {
"sectionsCompleted": 0,
"totalSections": 4
},
"two": {
"sectionsCompleted": 0,
"totalSections": 1
}
};
$cordovaFile.writeFile(url + "js/", "chapters.json", init, false)
.then(function (success) {
// success
}, function (error) {
// error
});
},
chapterProgress: function () {
return $http.get(url + "js/chapters.json").success(function (response) {
var json = JSON.parse(response);
return json;
}, function (error) {
alert(error);
});
}
}
});
Thank You so much for the help and time.

FuelUX spinbox - use custom strings array

Is it possible to leverage FuelUX spinbox to cycle/scroll through array of custom strings?
This is an example of what I mean, but it's a jQueryUI implementation:
Links to jsfiddle.net must be accompanied by code.
Please indent all code by 4 spaces using the code
toolbar button or the CTRL+K keyboard shortcut.
For more editing help, click the [?] toolbar icon.
http://jsfiddle.net/MartynDavis/gzmvc2ds/
Not out of the box, no.
You'd have to modify this portion of spinbox to make newVal be set by accessing your desired array instead of doing maths:
step: function step(isIncrease) {
//refresh value from display before trying to increment in case they have just been typing before clicking the nubbins
this.setValue(this.getDisplayValue());
var newVal;
if (isIncrease) {
newVal = this.options.value + this.options.step;
} else {
newVal = this.options.value - this.options.step;
}
newVal = newVal.toFixed(5);
this.setValue(newVal + this.unit);
},
Something quick and dirty based on FuelUX spinbox:
/*
* Fuel UX SpinStrings
* https://github.com/ExactTarget/fuelux
*
* Copyright (c) 2014 ExactTarget
* Licensed under the BSD New license.
*/
// -- BEGIN UMD WRAPPER PREFACE --
// For more information on UMD visit:
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
(function (factory) {
if (typeof define === 'function' && define.amd) {
// if AMD loader is available, register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// OR use browser globals if AMD is not present
factory(jQuery);
}
}(function ($) {
// -- END UMD WRAPPER PREFACE --
// -- BEGIN MODULE CODE HERE --
var old = $.fn.spinstrings;
// SPINSTRINGS CONSTRUCTOR AND PROTOTYPE
var SpinStrings = function SpinStrings(element, options) {
this.$element = $(element);
this.$element.find('.btn').on('click', function (e) {
//keep spinstrings from submitting if they forgot to say type="button" on their spinner buttons
e.preventDefault();
});
if ($.isPlainObject(options) && 'options' in options) {
if (!$.isArray(options.options)) {
delete options.options;
} else {
options.min = 0;
options.max = options.options.length - 1;
if (options.value && ((idx = options.options.indexOf(options.value)) > -1)) {
options.index = idx;
} else {
options.index = 0;
}
}
}
this.options = $.extend({}, $.fn.spinstrings.defaults, options);
if (this.options.index < this.options.min) {
this.options.index = this.options.min;
} else if (this.options.max < this.options.index) {
this.options.index = this.options.max;
}
this.$input = this.$element.find('.spinstrings-input');
this.$input.on('focusout.fu.spinstrings', this.$input, $.proxy(this.change, this));
this.$element.on('keydown.fu.spinstrings', this.$input, $.proxy(this.keydown, this));
this.$element.on('keyup.fu.spinstrings', this.$input, $.proxy(this.keyup, this));
this.bindMousewheelListeners();
this.mousewheelTimeout = {};
this.$element.on('click.fu.spinstrings', '.spinstrings-up', $.proxy(function () {
this.step(true);
}, this));
this.$element.on('click.fu.spinstrings', '.spinstrings-down', $.proxy(function () {
this.step(false);
}, this));
this.lastValue = this.options.value;
this.render();
if (this.options.disabled) {
this.disable();
}
};
// Truly private methods
var _applyLimits = function(value) {
// if unreadable
if (isNaN(parseFloat(value))) {
return value;
}
// if not within range return the limit
if (value > this.options.max) {
if (this.options.cycle) {
value = this.options.min;
} else {
value = this.options.max;
}
} else if (value < this.options.min) {
if (this.options.cycle) {
value = this.options.max;
} else {
value = this.options.min;
}
}
return value;
};
SpinStrings.prototype = {
constructor: SpinStrings,
destroy: function destroy() {
this.$element.remove();
// any external bindings
// [none]
// set input value attrbute
this.$element.find('input').each(function () {
$(this).attr('value', $(this).val());
});
// empty elements to return to original markup
// [none]
// returns string of markup
return this.$element[0].outerHTML;
},
render: function render() {
this.setValue(this.getDisplayValue());
},
change: function change() {
this.setValue(this.getDisplayValue());
this.triggerChangedEvent();
},
triggerChangedEvent: function triggerChangedEvent() {
var currentValue = this.getValue();
if (currentValue === this.lastValue) return;
this.lastValue = currentValue;
// Primary changed event
this.$element.trigger('changed.fu.spinstrings', currentValue);
},
step: function step(isIncrease) {
//refresh value from display before trying to increment in case they have just been typing before clicking the nubbins
this.setValue(this.getDisplayValue());
var newVal;
if (isIncrease) {
newVal = this.options.index + this.options.step;
} else {
newVal = this.options.index - this.options.step;
}
newVal = _applyLimits.call(this, newVal);
newVal = this.getOptionByIndex(newVal);
this.setValue(newVal);
},
getDisplayValue: function getDisplayValue() {
var inputValue = this.$input.val();
var value = (!!inputValue) ? inputValue : this.options.value;
return value;
},
/**
* #param string value
*/
setDisplayValue: function setDisplayValue(value) {
this.$input.val(value);
},
/**
* #return string
*/
getValue: function getValue() {
return this.options.value;
},
/**
* #param string val
*/
setValue: function setValue(val) {
var intVal = this.getIndexByOption(val);
//cache the pure int value
this.options.value = val;
this.options.index = intVal;
//display number
this.setDisplayValue(val);
return this;
},
value: function value(val) {
if (val || val === 0) {
return this.setValue(val);
} else {
return this.getValue();
}
},
/**
* Get string's position in array of options.
*
* #param string value
* #return integer
*/
getIndexByOption: function(value) {
ret = null;
if (this.options.options) {
ret = this.options.options.indexOf(value);
}
return ret;
},
/**
* Get option string by index.
*
* #param integer index
* #return string
*/
getOptionByIndex: function(value) {
ret = null;
if (this.options.options[value]) {
ret = this.options.options[value];
}
return ret;
},
disable: function disable() {
this.options.disabled = true;
this.$element.addClass('disabled');
this.$input.attr('disabled', '');
this.$element.find('button').addClass('disabled');
},
enable: function enable() {
this.options.disabled = false;
this.$element.removeClass('disabled');
this.$input.removeAttr('disabled');
this.$element.find('button').removeClass('disabled');
},
keydown: function keydown(event) {
var keyCode = event.keyCode;
if (keyCode === 38) {
this.step(true);
} else if (keyCode === 40) {
this.step(false);
} else if (keyCode === 13) {
this.change();
}
},
keyup: function keyup(event) {
var keyCode = event.keyCode;
if (keyCode === 38 || keyCode === 40) {
this.triggerChangedEvent();
}
},
bindMousewheelListeners: function bindMousewheelListeners() {
var inputEl = this.$input.get(0);
if (inputEl.addEventListener) {
//IE 9, Chrome, Safari, Opera
inputEl.addEventListener('mousewheel', $.proxy(this.mousewheelHandler, this), false);
// Firefox
inputEl.addEventListener('DOMMouseScroll', $.proxy(this.mousewheelHandler, this), false);
} else {
// IE <9
inputEl.attachEvent('onmousewheel', $.proxy(this.mousewheelHandler, this));
}
},
mousewheelHandler: function mousewheelHandler(event) {
if (!this.options.disabled) {
var e = window.event || event;// old IE support
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
var self = this;
clearTimeout(this.mousewheelTimeout);
this.mousewheelTimeout = setTimeout(function () {
self.triggerChangedEvent();
}, 300);
if (delta > 0) {//ACE
this.step(true);
} else {
this.step(false);
}
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
return false;
}
}
};
// SPINSTRINGS PLUGIN DEFINITION
$.fn.spinstrings = function spinstrings(option) {
var args = Array.prototype.slice.call(arguments, 1);
var methodReturn;
var $set = this.each(function () {
var $this = $(this);
var data = $this.data('fu.spinstrings');
var options = typeof option === 'object' && option;
if (!data) {
$this.data('fu.spinstrings', (data = new SpinStrings(this, options)));
}
if (typeof option === 'string') {
methodReturn = data[option].apply(data, args);
}
});
return (methodReturn === undefined) ? $set : methodReturn;
};
// value needs to be 0 for this.render();
$.fn.spinstrings.defaults = {
value: null,
index: 0,
min: 0,
max: 0,
step: 1,
disabled: false,
cycle: true
};
$.fn.spinstrings.Constructor = SpinStrings;
$.fn.spinstrings.noConflict = function noConflict() {
$.fn.spinstrings = old;
return this;
};
// DATA-API
$(document).on('mousedown.fu.spinstrings.data-api', '[data-initialize=spinstrings]', function (e) {
var $control = $(e.target).closest('.spinstrings');
if (!$control.data('fu.spinstrings')) {
$control.spinstrings($control.data());
}
});
// Must be domReady for AMD compatibility
$(function () {
$('[data-initialize=spinstrings]').each(function () {
var $this = $(this);
if (!$this.data('fu.spinstrings')) {
$this.spinstrings($this.data());
}
});
});
// -- BEGIN UMD WRAPPER AFTERWORD --
}));
// -- END UMD WRAPPER AFTERWORD --
Mark-up & style:
<style>
.spinstrings {position:relative;display:inline-block;margin-left:-2px}
.spinstrings input {height:26px;color:#444444;font-size:12px;padding:0 5px;margin:0 !important;width:100px}
.spinstrings input[readonly] {background-color:#ffffff !important}
.spinstrings .spinstrings-arrows {position:absolute;right:5px;height:22px;width:12px;background:transparent;top:1px}
.spinstrings .spinstrings-arrows i.fa {position:absolute;display:block;font-size:16px;line-height:10px;width:12px;cursor:pointer}
.spinstrings .spinstrings-arrows i.fa:hover {color:#307ecc}
.spinstrings .spinstrings-arrows i.fa.spinstrings-up {top:0}
.spinstrings .spinstrings-arrows i.fa.spinstrings-down {bottom:0}
</style>
<div class="spinstrings" id="mySpinStrings">
<input type="text" class="spinstrings-input" readonly="readonly">
<div class="spinstrings-arrows"><i class="fa fa-caret-up spinstrings-up"></i><i class="fa fa-caret-down spinstrings-down"></i></div>
</div>
Init as:
$('#mySpinStrings', expiryControlsW).spinstrings({
options: ['day', 'week', 'month'],
value: 'week'
});
That's it, folks!

Unable to get my data from $firebaseObject using AngularFire in a controller. View/ng-repeat works fine

I have different sections in Firebase with normalized data, and I have routines to get the information, but I cannot loop through the returned records to get data. I want to use the keys in the $firebaseArray() to get data from other $firebaseObject().
GetOneTeam() .... {
var DataRef = GetFireBaseObject.DataURL(Node + '/'); // xxx.firebaseio.com/Schedules/
var OneRecordRef = DataRef.child(Key); // Schedule Key - 1
return $firebaseObject(OneRecordRef);
}
...
var Sched = GetOneSchedule('Schedules', 1);
... // For Loop getting data - Put in HomeId
var TeamRec = GetOneTeam('Teams', HomeId);
var Name = TeamRec.TeamName; // Does not TeamName value from Schedule/1
The following is more of the actual code in case the snippet above is not clear enough. Sample common routine for getting data:
angular.module('MyApp')
.constant('FIREBASE_URL', 'https://xxxxxxxx.firebaseio.com/');
angular.module('MyApp')
.factory('GetFireBaseObject', function(FIREBASE_URL) {
return {
BaseURL: function() {
return new Firebase(FIREBASE_URL);
},
DataURL: function(Node) {
return new Firebase(FIREBASE_URL + Node);
}
};
}
);
// Common code for getting Array/Object from Firebase.
angular.module('MyApp')
.factory("FireBaseData", ["$firebaseArray", "$firebaseObject", "GetFireBaseObject",
function($firebaseArray, $firebaseObject, GetFireBaseObject) {
return {
AllRecords: function(Node) {
var DataRef = GetFireBaseObject.DataURL(Node + '/');
return $firebaseArray(DataRef);
},
OneRecordAllChildren: function(Node, Key) {
var DataRef = GetFireBaseObject.DataURL(Node + '/');
var ParentRecordRef = DataRef.child(Key);
return $firebaseArray(ParentRecordRef);
},
OneRecord: function(Node, Key) {
var DataRef = GetFireBaseObject.DataURL(Node + '/');
var OneRecordRef = DataRef.child(Key);
return $firebaseObject(OneRecordRef);
},
AddRecord: function(Node, Record) {
var DataRef = GetFireBaseObject.DataURL(Node + '/');
var AddRecordRef = DataRef.child(Record.Key);
AddRecordRef.update(Record);
return $firebaseObject(AddRecordRef); // Return Reference to added Record
},
DeleteRecord: function(Node, Key) {
var DataRef = GetFireBaseObject.DataURL(Node + '/');
var DeleteRecordRef = DataRef.child(Key);
DeleteRecordRef.remove();
}
};
}
]);
Individual Controller's retrieval of records from firebase.io:
angular.module('MyApp').service("ScheduleData", ["FireBaseData",
function(FireBaseData) {
var DataPath = 'Schedules';
this.AllSchedules = function() {
return FireBaseData.AllRecords(DataPath);
};
this.AddSchedule = function(GameInfo) {
return FireBaseData.AddRecord(DataPath, GameInfo);
};
this.DeleteSchedule = function(GameKey) {
FireBaseData.DeleteRecord(DataPath, GameKey);
};
this.GetOneSchedule = function(GameKey) {
return FireBaseData.OneRecord(DataPath, GameKey);
};
}
]);
// Structure of a record, including named fields to come from another object (Team/Venue using the OneRecord FireBaseData call to get a $firebaseObject
angular.module('MyApp').factory("ScheduleRecord", function() {
return {
Clear: function(GameInfo) {
GameInfo.Key = "";
GameInfo.HomeTeamId = "";
GameInfo.HomeTeamName = "";
GameInfo.AwayTeamId = "";
GameInfo.AwayTeamName = "";
GameInfo.VenueId = "";
GameInfo.VenueName = "";
GameInfo.GameDate = "";
GameInfo.GameTime = "";
}
};
}
);
Controller module start:
angular.module('MyApp').controller('ScheduleCtrl', ["$scope", "ScheduleData", "ScheduleRecord", "TeamData", "VenueData",
function ($scope, ScheduleData, ScheduleRecord, TeamData, VenueData) {
var ClearEditData = function() {
$scope.ScheduleEditMode = false;
ScheduleRecord.Clear($scope.schedule);
};
var GameSchedules = ScheduleData.AllSchedules();
This next piece is where my question lies. Once the promise returns the static schedule list, I want to loop through each record and translate the Team Id (Home/Away) and Venue Id to the names.
GameSchedules.$loaded().then(function() {
angular.forEach(GameSchedules, function(GameInfo) {
var HomeTeam = TeamData.GetOneTeam(GameInfo.HomeTeamId);
GameInfo.HomeTeamName = HomeTeam.Name;
The GetOneTeam returns a $firebaseObject, based on the HomeTeamId child record. This returns null all the time.
This is the TeamData.GetOneTeam return using the FireBaseData as well.
angular.module('MyApp').service("TeamData", ["FireBaseData",
function(FireBaseData) {
var DataPath = 'Teams';
this.AllTeams = function() {
return FireBaseData.AllRecords(DataPath);
};
this.AddTeam = function(TeamInfo) {
return FireBaseData.AddRecord(DataPath, TeamInfo);
};
this.DeleteTeam = function(TeamKey) {
FireBaseData.DeleteRecord(DataPath, TeamKey);
};
this.GetOneTeam = function(TeamKey) {
return FireBaseData.OneRecord(DataPath, TeamKey);
};
}
]);
As I have a Firebase Object, how can I get my named data objects from the $firebaseObject?
This is a mess. Use $firebaseArray for collections, not $firebaseObject. Most of these strange wrapper factories are unnecessary. AngularFire services already have methods for add, remove, and so on, and all these factories attempt to make AngularFire into a CRUD model and don't actually provide any additional functionality or enhancements.
app.factory('Ref', function(FIREBASE_URL) {
return new Firebase(FIREBASE_URL);
});
app.factory('Schedules', function($firebaseArray, Ref) {
return $firebaseArray(Ref.child('Schedules'));
});
// or if you want to pass in the path to the data...
//app.factory('Schedules', function($firebaseArray, Ref) {
// return function(pathToData) {
// return $firebaseArray(Ref.child(pathToData));
// };
//});
app.factory('Schedule', function($firebaseObject, Ref) {
return function(scheduleId) {
return $firebaseObject(Ref.child('Schedules').child(scheduleId));
}
});
app.controller('...', function(Schedules, Schedule, Ref) {
$scope.newSchedule(data) {
Schedules.$add(data);
};
$scope.removeSchedule(key) {
Schedules.$remove(key);
};
$scope.updateSchedule(key, newWidgetValue) {
var rec = Schedules.$getRecord(key);
rec.widgetValue = newWidgetValue;
Schedules.$save(rec);
};
// get one schedule
var sched = Schedule(key);
sched.$loaded(function() {
sched.widgetValue = 123;
sched.$save();
});
});

Bootstrap Typeahead with Backbone memory leak

I am using Bootstrap Typeahead along with Backbone to display data fetched from the server based on user input. The backing Backbone collection, which is used to store the fetched data, is created once on app startup. The collection is re-used for every new search in Typeahead.
The issue I am having is the browser memory usage keeps going up every time users searches for something. My question is really how can I make sure the old result/data in collection is garbage collected when the new one comes in? Also is re-using the same collection for new search the right way to go?
The collection js file
define([
"data",
"backbone",
"vent"
],
function (data, Backbone, vent) {
var SearchCollection = Backbone.Collection.extend({
model:Backbone.Model,
url:function () {
return data.getUrl("entitysearch");
},
initialize:function (models, options) {
var self=this;
this.requestAborted = false;
this.categories = (options && options.categories) || ['counterparty', 'company', 'user'];
this.onItemSelected = options.onItemSelected;
this.selectedId = options.selectedId; // should be prefixed with type eg. "company-12345"
_.bindAll(this, "entitySelected");
vent.bindTo(vent, "abortSearchAjax", function () {
this.requestAborted = true;
}, this);
},
search:function (criteria) {
var self = this,
results = [];
// abort any existing requests
if (this.searchRequest) {
this.searchRequest.abort();
}
self.requestAborted= false;
this.searchRequest = this.fetch({
data:$.param({
query:criteria,
types: this.mapEntityTypesToCodes(this.categories),
fields:'ANY',
max: 500
})
})
.done(function(response, textStatus, jqXHR) {
if (!self.requestAborted){
results = self.processResponse(response);
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
if(errorThrown === "Unauthorized" || errorThrown === "Forbidden") {
alert("Either you do not have the right permissions to search for entities or you do not have a valid SSO token." +
" Reload the page to update your SSO token.");
}
})
.always(function(){
if (!self.requestAborted){
self.reset(results);
self.trigger('searchComplete');
}
});
},
/**
* Backbone parse won't work here as it requires you to modify the original response object not create a new one.
* #param data
* #return {Array}
*/
processResponse:function (response) {
var self = this,
result = [];
_.each(response, function (val, key, list) {
if (key !== 'query') {
_.map(val, function (v, k, l) {
var id;
v.type = self.mapEntityShortName(key);
id = v.id;
v.id = v.type + '-' + v.id;
v.displayId = id;
});
result = result.concat(val);
}
});
return result;
},
mapEntityTypesToCodes:function (types) {
var codes = [],
found = false;
_.each(types, function(el, index, list) {
{
switch (el) {
case 'counterparty':
codes.push('L5');
found = true;
break;
case 'company':
codes.push('L3');
found = true;
break;
case 'user':
codes.push('user');
found = true;
break;
}
}
});
if (!found) {
throw "mapEntityTypesToCodes - requires an array containing one or more types - counterparty, company, user";
}
return codes.join(',');
},
mapEntityShortName: function(name) {
switch (name) {
case 'parties':
return 'counterparty';
break;
case 'companies':
return 'company';
break;
case 'users':
return 'user';
break;
}
},
entitySelected:function (item) {
var model,
obj = JSON.parse(item),
data;
model = this.get(obj.id);
if (model) {
model.set('selected', true);
data = model.toJSON();
this.selectedId = obj.id;
//correct the id to remove the type eg. company-
data.id = data.displayId;
this.onItemSelected && this.onItemSelected(data);
} else {
throw "entitySelected - model not found";
}
},
openSelectedEntity: function() {
var model = this.get(this.selectedId);
if (model) {
vent.trigger('entityOpened', {
id: this.selectedId.split('-')[1],
name: model.get('name'),
type: model.get('type')
});
}
},
entityClosed:function (id) {
var model;
model = this.where({
id:id
});
if (model.length) {
model[0].set('selected', false);
}
}
});
return SearchCollection;
});
The View js file
define([
"backbone",
"hbs!modules/search/templates/search",
"bootstrap-typeahead"
],
function (Backbone, tpl) {
return Backbone.Marionette.ItemView.extend({
events: {
'click .action-open-entity': 'openEntity'
},
className: 'modSearch',
template:{
type:'handlebars',
template:tpl
},
initialize:function (options) {
_.bindAll(this, "render", "sorter", "renderSearchResults", "typeaheadSource");
this.listen();
this.categoryNames = options.categoryNames;
this.showCategoryNames = options.showCategoryNames;
this.autofocus = options.autofocus;
this.initValue = options.initValue;
this.disabled = options.disabled;
this.updateValueOnSelect = options.updateValueOnSelect;
this.showLink = options.showLink;
this.resultsLength = 1500;
},
listen:function () {
this.collection.on('searchComplete', this.renderSearchResults);
this.collection.on('change:selected', this.highlightedItemChange, this);
},
resultsFormatter:function () {
var searchResults = [],
that = this;
this.collection.each(function (result) {
searchResults.push(that._resultFormatter(result));
});
return searchResults;
},
_resultFormatter:function (model) {
var result = {
name:model.get('name'),
id:model.get('id'),
displayId: model.get('displayId'),
aliases:model.get('aliases'),
type:model.get('type'),
marketsPriority:model.get('marketsPriority')
};
if (model.get('ssoId')) {
result.ssoId = model.get('ssoId');
}
return JSON.stringify(result);
},
openEntity: function() {
this.collection.openSelectedEntity();
},
serializeData:function () {
return {
categoryNames:this.categoryNames,
showCategoryNames: this.showCategoryNames,
initValue:this.initValue,
autofocus:this.autofocus,
showLink:this.showLink
};
},
onRender:function () {
var self = this,
debouncedSearch;
if (this.disabled === true) {
this.$('input').attr('disabled', 'disabled');
} else {
debouncedSearch = _.debounce(this.typeaheadSource, 500);
this.typeahead = this.$('.typeahead')
.typeahead({
source: debouncedSearch,
categories:{
'counterparty':'Counterparties',
'company':'Companies',
'user':'Users'
},
minLength:3,
multiSelect:true,
items:this.resultsLength,
onItemSelected:self.collection.entitySelected,
renderItem:this.renderDropdownItem,
matcher: this.matcher,
sorter:this.sorter,
updateValueOnSelect:this.updateValueOnSelect
})
.data('typeahead');
$('.details').hide();
}
},
onClose: function(){
this.typeahead.$menu.remove();
},
highlightedItemChange:function (model) {
this.typeahead.changeItemHighlight(model.get('displayId'), model.get('selected'));
},
renderSearchResults:function () {
this.searchCallback(this.resultsFormatter());
},
typeaheadSource:function (query, searchCallback) {
this.searchCallback = searchCallback;
this.collection.search(query);
},
/**
* Called from typeahead plugin
* #param item
* #return {String}
*/
renderDropdownItem:function (item) {
var entity,
marketsPriority = '',
aliases = '';
if (!item) {
return item;
}
if (typeof item === 'string') {
entity = JSON.parse(item);
if (entity.marketsPriority && (entity.marketsPriority === "Y")) {
marketsPriority = '<span class="marketsPriority">M</span>';
}
if (entity.aliases && (entity.aliases.constructor === Array) && entity.aliases.length) {
aliases = ' (' + entity.aliases.join(', ') + ') ';
}
if (entity.type === "user"){
entity.displayId = entity.ssoId;
}
return [entity.name || '', aliases, ' (', entity.displayId || '', ')', marketsPriority].join('');
}
return item;
},
matcher: function(item){
return item;
},
/**
* Sort typeahead results - called from typeahead plugin
* #param items
* #param query
* #return {Array}
*/
sorter:function (items, query) {
var results = {},
reducedResults,
unmatched,
filteredItems,
types = ['counterparty', 'company', 'user'],
props = ['displayId', 'name', 'aliases', 'ssoId'],
type,
prop;
query = $.trim(query);
for (var i = 0, j = types.length; i < j; i++) {
type = types[i];
filteredItems = this._filterByType(items, type);
for (var k = 0, l = props.length; k < l; k++) {
prop = props[k];
unmatched = [];
if (!results[type]) {
results[type] = [];
}
results[type] = results[type].concat(this._filterByProperty(query, filteredItems, prop, unmatched));
filteredItems = unmatched;
}
}
reducedResults = this._reduceItems(results, types, this.resultsLength);
return reducedResults;
},
/**
* Sort helper - match query string against a specific property
* #param query
* #param item
* #param fieldToMatch
* #param resultArrays
* #return {Boolean}
* #private
*/
_matchProperty:function (query, item, fieldToMatch, resultArrays) {
if (fieldToMatch.toLowerCase().indexOf(query.toLowerCase()) === 0) {
resultArrays.beginsWith.push(item);
} else if (~fieldToMatch.indexOf(query)) resultArrays.caseSensitive.push(item)
else if (~fieldToMatch.toLowerCase().indexOf(query.toLowerCase())) resultArrays.caseInsensitive.push(item)
else if(this._fieldConatins(query, fieldToMatch, resultArrays)) resultArrays.caseInsensitive.push(item)
else return false;
return true;
},
_fieldConatins:function (query, fieldToMatch, resultArrays) {
var matched = false;
var queryList = query.split(" ");
_.each(queryList, function(queryItem) {
if(fieldToMatch.toLowerCase().indexOf(queryItem.toLowerCase()) !== -1) {
matched = true;
return;
}
});
return matched;
},
/**
* Sort helper - filter result set by property type (name, id)
* #param query
* #param items
* #param prop
* #param unmatchedArray
* #return {Array}
* #private
*/
_filterByProperty:function (query, items, prop, unmatchedArray) {
var resultArrays = {
beginsWith:[],
caseSensitive:[],
caseInsensitive:[],
contains:[]
},
itemObj,
item,
isMatched;
while (item = items.shift()) {
itemObj = JSON.parse(item);
isMatched = itemObj[prop] && this._matchProperty(query, item, itemObj[prop].toString(), resultArrays);
if (!isMatched && unmatchedArray) {
unmatchedArray.push(item);
}
}
return resultArrays.beginsWith.concat(resultArrays.caseSensitive, resultArrays.caseInsensitive, resultArrays.contains);
},
/**
* Sort helper - filter result set by entity type (counterparty, company, user)
* #param {Array} items
* #param {string} type
* #return {Array}
* #private
*/
_filterByType:function (items, type) {
var item,
itemObj,
filtered = [];
for (var i = 0, j = items.length; i < j; i++) {
item = items[i];
itemObj = JSON.parse(item);
if (itemObj.type === type) {
filtered.push(item);
}
}
return filtered;
},
/**
* Sort helper - reduce the result set down and split between the entity types (counterparty, company, user)
* #param results
* #param types
* #param targetLength
* #return {Array}
* #private
*/
_reduceItems:function (results, types, targetLength) {
var categoryLength,
type,
len,
diff,
reduced = [],
reducedEscaped = [];
categoryLength = Math.floor(targetLength / types.length);
for (var i = 0, j = types.length; i < j; i++) {
type = types[i];
len = results[type].length;
diff = categoryLength - len;
if (diff >= 0) { // actual length was shorter
reduced = reduced.concat(results[type].slice(0, len));
categoryLength = categoryLength + Math.floor(diff / (types.length - (i + 1)));
} else {
reduced = reduced.concat(results[type].slice(0, categoryLength));
}
}
_.each(reduced, function(item) {
item = item.replace(/\'/g,"`");
reducedEscaped.push(item);
});
return reducedEscaped;
}
});
});

Resources