My example :
var UserModel = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.color == 'muave') {
return true;
}
}
});
var me = new UserModel({ id: '123', silent: true });
me.on('invalid', function (model, error) {
console.log('event invalid')
});
me.set('color', 'muave');
me.set('name', 'sasha');
if(!me.isValid()){
me.clear()
}
I do not want to cleane all model / only the properties that do not pass validation.
It's possible ?
thank you very much
UPD:
I do not know how good the way I did?
var UserModel = Backbone.Model.extend({
validate_prop :['color','name'],
validate: function(attrs) {
if (attrs.color == 'muave' || attrs.name == 'sasha') {
return true;
}
},
positiveValid: function(){
for(var i = 0 ;i< this.validate_prop.length;i++){
this.unset(this.validate_prop[i])
}
}
});
var me = new UserModel({ id: '123', silent: true });
me.on('invalid', function (model, error) {
console.log('event invalid')
});
me.set('color', 'muave');
me.set('name', 'sasha');
if(!me.isValid()){
me.positiveValid();
me.isValid() //clear validationError: true
}
Maybe there is a more universal solution?
thank you for your help .
This is roughly how I would go about it (note this is pseudo code I may have a mistake in there):
// An object of validation rules
validation: {
color: {
type: 'String',
options: {
notEqualTo: 'muave'
}
},
name: {
type: 'String',
options: {
notEqualTo: 'sasha'
}
},
},
initialize: function () {
this.validation = validation || {};
},
validate: function (attrs) {
this.invalidAttrs = {};
// Loop through validation object, return the first invalid attribute.
var invalid = _.find(this.validation, function (options, attr) {
var type = attr.type || 'String';
return this['validate' + attr.type](attr, options);
}, this);
// If there was an invalid attribute add it invalidAttrs.
// Also trigger an event.. could be useful.
if (invalid) {
this.invalidAttrs[attr] = invalid;
this.trigger('invalid:' + attr, invalid);
}
return invalid;
},
validateString: function (attr, options) {
if (options.notEqualTo && this.get(attr) === options.notEqualTo) {
return {
'String cannot be equal to ' + options.notEqualTo
};
}
}
Then you could do:
if(!me.isValid()){
_.each(me.invalidAttrs, function(reason, attr) {
me.unset(attr);
});
}
This would only find the error and then stop, you could quite easily change it to accumulate all errors. The fundamental thing is that you have a validation object you loop through which has validation rules for your model. When you say type: 'Number' in that object it will call validateNumber(attr, options) to determine if a number is valid. I would also recommend adding required: true/false for all types.
Here is a more involved and real world example of what I was talking about:
////////////////
// Validation //
////////////////
/**
* Syntax for validation rules
* #type {Object|Function}
* validation: {
* <attributeNameA>: {
* required: false, // default, can be omitted
* type: 'string', // default, can be omitted
* options: {
* // type-specific validation rules, see validator
* // documentation
* }
* },
* <attributeNameB>: {
* validator: function (value, attrs) {
* // optional custom validator, validated first if present
* },
* required: true, // validated second if present
* type: 'date', // type validation is validated last
* options: {
* // type-specific validation rules
* }
* }
* }
*/
/**
* Validate model
* #param {Object} attrs attributes
* #param {Object} options options
* #return {Object|undefined} returns validation error object
* or undefined if valid
*/
validate: function(attrs, options) {
if (!this.validation) {
return;
}
options = options || {};
var invalidAttrs = {},
validAttrs = {},
isValid = true,
isPartial,
validation = _.result(this, 'validation');
function checkValidity(rule, attr) {
/* jshint validthis:true */
var error = rule && this.validateAttr(rule, attr, attrs[attr], options);
if (error) {
invalidAttrs[attr] = error;
isValid = false;
} else if(rule) {
validAttrs[attr] = true;
}
}
if (options.validateAttr) {
var attr = options.validateAttr;
checkValidity.call(this, validation[attr], attr);
this.attrState = this.attrState || {};
this.attrState[attr] = isValid;
isPartial = _.difference(_.keys(validation), _.keys(this.attrState)).length > 0;
} else {
_.each(validation, checkValidity, this);
}
if (!options.silent) {
isValid = options.validateAttr ? _.all(this.attrState) : isValid;
this.triggerValidationEvents(isValid, validAttrs, invalidAttrs, isPartial);
}
// Note: successful validation needs to return undefined / falsy
return isValid ? void 0 : invalidAttrs;
},
/**
* Validate attribute with a given value
* #param {Object} rule validation rule from validation object
* #param {String} attr attribute name
* #param {Mixed} value value
* #param {Object} options options
* #return {Object|undefined} returns validation error object
* or undefined if valid
*/
validateAttr: function(rule, attr, value, options) {
var error;
options = _.extend({}, options, rule.options);
if (rule.required && (value == null || value === '')) {
return {
reasonCode: 'required'
};
}
if (rule.type) {
if (!this['validate' + rule.type]) {
console.error('No validation found for type:', rule.type);
}
error = this['validate' + rule.type].call(this, value, options);
if (error) {
return error;
}
}
if (rule.validator) {
return rule.validator.call(this, value, options);
}
},
/**
* Convenience method: check if a single attribute is valid
* #param {String} attr attribute name to be validated
* #return {Boolean} true if valid
*/
isValidAttr: function(attr, value, options) {
var obj = {};
obj[attr] = value;
var attrs = _.extend({}, this.attributes, obj);
var error = this.validate(attrs, _.extend({}, options, {
validateAttr: attr
}));
if (error) {
this.validationError = this.validationError || {};
this.validationError[attr] = error;
} else if (this.validationError && attr in this.validationError) {
delete this.validationError[attr];
}
return error === void 0;
},
/**
* Triggers events for valid and invalid attributes after validation
* #param {Boolean} isValid was validation successful?
* #param {Object} validAttrs attributes that were validated successfully.
* #param {Object} invalidAttrs attributes for which validation failed.
* #param {Boolean} isPartial is it validating only a part of the model?
*/
triggerValidationEvents: function(isValid, validAttrs, invalidAttrs, isPartial) {
if (!isPartial) {
this.trigger('validated', isValid, this, invalidAttrs);
this.trigger('validated:' + (isValid ? 'valid' : 'invalid'), this, invalidAttrs);
}
_.each(validAttrs, function(value, attr) {
this.trigger('validated:' + attr, this, true);
}, this);
_.each(invalidAttrs, function(error, attr) {
this.trigger('validated:' + attr, this, false, error);
}, this);
}
Related
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.
i have an angular base dashboard application ,in the application dashboard page icon ,when i click the particular icon ,the icon should be redirected to the routing page with menu breadcrumb with that icon.Instead of displaying image have to include font-icon in that section.
displayImg is for displaying image and displayName is for displaying name
Below code is working fine with image.now i have to replace with font-icon.
how to do this.
here is an working plunker code:
https://plnkr.co/edit/vKekaq7pCAqOpdXXJJYc?p=info
In the example when you click the detail , home icon is in the menu,when you click the product ,product icon will be there,order means order icon will be there its all images, which is mentioned under particular route here.
.state('home.product', {
url: '/product',
views: {
'main#': {
templateUrl: 'product.html',
}
},
data: {
displayName: 'product',
displayImg: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAdVBMVEVNTU3///9MTExNTUzz8/P8/Pz39/f6+vpERESgoKCoqKhJSUlCQkJGRkY/Pz86Ojqbm5uRkZHv7+9TU1NoaGitra3g4ODCwsLm5ubKysq8vLxaWlrc3NzR0dFycnKFhYWHh4d5eXm0tLRsbGw1NTVYWFiOjo5/6CiKAAATcklEQVR4nN1d62KrrBJV4zWiJra5NclOm57d93/EwyAgICoYSLO//GhtM8osBWaYWQ5B+F//BN2vJAlHDhLNgWPZMREb2fGmO4RZSv/MMvo1P2Df8AMr2WxWdnA5G1kTNQnClP3FDpI0ZSdk7IRMFgl7kaFsMieru9yg6QlZ86YTQJjG7N/0IInZxdlBGmeySBjHoalsYiWbKrLDpm3UTAAhxx1bATSWtQOoyqb9wQI1MbSg77l2T/BJT3sI0EZN6MUBn3OeoXR2P9fnexuOys53USs14RtqLZ7QRdOkXZeojMq6XLfGAB/oorRphtA3wDC5bMs8CoJVtIpQtN3jkzyPQSobqK34GYPh9SuvMD4AiH9GVf51De0BLlEzWKy0zRM8nZsS8FGABGPzftIq7WoMsnsbLL015gDTO8bX4eIAVxhvWb9/pGwm99RFO3vot4vG67e6e34KwAAwvhWtX4CZaPGdP0H8zfGnyqMelwwQ99Uyr7bHkM7rHgB2Fj/zMwaxq3i5NXh6mQAYkQF5uzC30s0YFCxURiy+sdJWtzGLT3/qCmBMAoRPVX+fVIBOnqBk8Z2Pwd25JtPLPEB8AJOOczNBZdkK2G0XjYscddOnEUCYdFC+PibjTS8fScHSWzMhe9zmiKpvChC+QdXm2F3FXRdlCJ0CvNzynGtvDhDLRnn+eRH7qguABKFLgNdvVK2WAYQfFaKTjpsxSC1+Hy54bAzixdEH9l5W0WKA8Luszx/ZknurVTPJQmLx7W+NRjZpd28wfT4EEGTL+u8uTtwEHqjFdwEw3G8DMr08DBD+RAHxdMIHu2gmWfzHuuj1C5HFkROAcFCh2976CWrVZBb/IYDXP02HzxlA4s39uTJdHol3BQYAp29j8vFel0ZK2wCED3g6ifkTHFEzeBBgtntDpbnSNgDBm0N/d1m/8likZjB7ZjgKMAnbTY0iO6UtZSNUb/Ymc+GYmrLFt3qCaXj5qbH34hUg/M7rn8viJyhbfKsz01M3ffoGiA/wxApxqyUAs4zEvLMFZ97P3fB7AsCAeDrvHwvUTDuLP7FmCfVnxruITp/PAQjflHVwj3VqTqVFwDHiFt/81hw3qzoaUcQXQJDFns6mDVNba8YsvjHA/WeZl6sJRXwBhJ95+XmxBNjHvM266PVPXTlV2la2qv9c59UUIQXmAMPk8N6U7pW2lC2b90OczY9BBikw7KJJ1t5JbKmPy/8OwAC8ufMuNeuiFOEgC6SJBRw3AYktvQBAfFCioIiNAFKLP9tF95908fDbXZTLRnnzc0nDOYCqxR95gtevpnqC0payq6ruMnSTkRXZ4usBHr7rKlquiE/Zqv4+TK48FIuvA5jcIXP0RKUtZUlMZxQgQdJbfM0YbIsKRc9W2lK2RCXJ0I2uPIIBQJY6SvbbCkK7rw0QH2BPZ7sPRxdIgR5gmLDE+8sDhJ89LWBo2gMFIMUHsZdIbuUlAfYWsia0AA3tKwiHTzC9n1ls6cUBRoIIBMvbTJ03Mw2vrS3e+tjZPwGQyQItIJYByrw2AvC4xYujYSuvDFAQgUnnKCyOVV4b/rH9XxVpzvxHAMJ/q/9t5UyAzGsr6rEz/xGA8Kk3zKIPeW1tEOnOfIXVhAFApmb5FgsAGcJuktk3//4TxN80ewEg57WRX0e0+g8AjPKjALBDyF2196GZeOUuqp/sy3cxvCHz2tIrXej+wwCjCjopB0gtPpt78Grihr3t11DawFUbiqzy6taKtC8xy52weG/JyDCv/QR1PEAUbcQxOMJrC4/rNxLTflGAq4Es/Qd22giBXAkxanhtGV5awMr+Nbvo2BjE+FjCWI6jjfHaTjw681oA9SLANDpInsw8rw3PqzdUvRxA7RgE4sY1GwE44LWJSbrjpslfH2CUN5tjvwJUY6hyljtJ24v0skdaBEhNFL4UwFWJ3tapsB7ECFIJkpTlTtqfpmn+nJKEP8osvb87oXL5AUj5KH2Q4vCNEWyzMV5b+oUCwmJhIUhyZnLifKCXANjLVvA0wh5g2vGSo/qrf3VR4rWlp6Y7k+Y9+lvDOU8vAJCJYO/s69I/B+AlsxEVNFcGSea1JeucXRyP3s99KrylcCQvLb0OwCgvfyRu3/6z6Xm7aCfMmyKvjSBkF8e3iBHoSZ8VAlS/DhBCTvtQAHj6wp2sv1y+ZpBCmdcGCAVXjQ5jvtBq73/r0kwR32HDex82TCmxTrwcQajjtWGE8sXxgFy3ot96gkDxrwKMKgj9CpN9vCbDT7ocINTy2tb54OIRqjsua59LZDz1XwFYVRC+7xOgl21N1JEvhxHqeW3rXLOaUChXCb5o3r0p+XyAKN/uQwHgaYS3ixHqLT5+hrqLY4ycckVuTQvvizwdYFTnheRwHTivVb1cXnCDKGW5kyIfu3slKgn7gT37BFilTwWIvbMdmzwJlX9X1kPiEv1HXvB5SMpyZxShVpGIvNHCk3RZdviWvTmvAKv6+0PMDx43FZq4HFr3AMUsd2cPRxWJ8vwmJemuX3X1FIBV/XXqiwbQt3KmLtfZw0GWO+sQTp1Z5XKS7nKjjoRHgEArETpPii0WTC+Tl+sQanhtgHBGEexNSNa23cCA9AcQDz/sIvdLBfrWyszlCEIdrw0jnFckqqPuFTPaCfiLhh4AgveSCsb7uIlQZHA5QKjlta3NVvSrvPzcCyuP9uO9MX+Z0hggXhy9HxJhAr/cKupszF0OI9Tz2ta5oSLAgTyJ4QIgZU690GwPkNEsOUB45zYyvFy+TvS8NmYPTRQpm/OHGC7Y8xvsAiBeHN0oVZYATD9YyQKjy+VFxv1WMcutsfhTipT133XLJ7kw2xckWO4AINCd93zZmoXtGi9qbC6XF8xCSFnuNFurFn/uTiNOSiaPkvgZDwPE19jFfWQlPP7AnbO6nLHFNxhX8PqV0FfDDzaxLgS4wotSgYwXpjRIZHe5EYufqBbfbOIAUnLa3/Lk9AVv4S8EiNcxJ+F+xXfqXFtejln8Aa9NtvjmTjEekIzGAqpdPmvNpGxw3bz+3AsdIl7/5R1Cu+gZvRy1+ENem2Txbab+sqOx9JZ5i1Rvbva6EUKbNuwBHrdCls/yfnUWX8NrEy2+pW2D6hZXoZxaSu6/OcAS/S3Sfs7qKmosnrOIxdfx2tZoKUD4QHWLpL9fGfN0DDTCY5mVxwKAyYm81bF8Us7XGUvcy7y23uIv807gLYFMDLHTQPK0RjAfn7gLkiYZcXQfMqvY4nOWrFi9JeMWf7H7VaJ8wwKZEIXc36D+wJRGsOrcC37DcUMqajzmNzCLL/PasCfHLP5D/iWeEC8dwKSbMKKJGhngvRzFelm3bhp+0DGiFl/NcnOL/6gDDUvyvusnjBYwlIXEe9zfjJAWtJFklwCkFn/AawupxXewQijR+S7QAzOgBaxU2bJ56yJ4FCB3hx53bQlCmdfW/UUsvpslEB6QEPrj3snhG7GXboisUOKDNN0WaLRelr1rCwjVLHfc2UOHi9goR9uLUFb0cgOMnWxU8aBWNyGBhxA5AwgI5Sw3G+l9zNvRIhZJi1ji6awI9s2Fu9YZLcenue7iptWYN4nQEYTIJUA4AHa50GHaTVmjOlqnfS3cmL4V4BIgWHxtlptbfJexTqh02dd/wu7KHTs+3Ck4rktaL8tdFw2IxacAZV4bs/iuEyqoJPmU4bsQ+x/mXLsFKFh8mdfGs9xOAQaQ84NXWhIFYJ+nc9tFxy0+z3K7Bgg/qvrcJx4A4OGMnWtF1lnTzOIPeG1Sltt1fhCvk+8cIOTLh93ZWdPU4g95bSTL7QkgPoiaDQW4aTSJB4dN6yw+z3L7A4g/zZ4A3De6CWlw3eVNDyz+MMvtKYWdr4kd3CGvT9Aky+0rR59vqN31C9DE4vsBuKJ2qpjKj7homme5FV5bb/E9AYwows1EfsRJ07k25p2xFbA/gJ2vkWSbSpZ1Pr/xmPckr80DwA5hnBWVX4DaLLfAa/MHkCDEK7VCyo+4NBNUVpPlHuG1OQYICGEpWoj5ER8meJjlHue1OQUIqxryHmfuF6Auy80tvleAQbUhsdlCmx9x2DRYfDnLzS2+X4A6i+/Fx8gLHiAy5LW5ClCpFt+XGyxkuc15bU40Uiy+Nz9/Ia/NgUaqxfflBi/ntT2qESBM4qSz+B5Xavostxmv7TGNMEJYqRVKfsQ1QH2W25jX9ohGYPHTzh56XWvrstw2vLblGlUFWakVfqMl2iy3Ja9tqUbVJumshedoCfDaGMAuy72A17ZIo97iewXYZ7mV6i2ZHa9tgSy3+H4BuuO1WcsOLL4fgNziy9VblvHa7GQJwoxZfI/Rkg5h7IbXZiMLCDO2AvYZLVn3AF3w2sxlMcKMWXyv0ZJ1D9ANr81UVrL4/gAShLFjXpuZrGjxPQIkK2APvDaTSG1v8X0C9Mdrm5WdiHk7vbceeW2TslMxb7edZyzL7YbXNgGQ5i0SNebtfH7zzWsblyUIU2bx/S1kJrPcPgEShKka8/Zgoaay3B67aEAQwkqt8B4teQqvTRJhkdqCUE4K79GSYZbbC69NfYIk5p2FSszbhwke8tpYzNs1r00VGc9yu+08T+W1SSImWW4XTfMs93N4bWKkdj7L7aRpZvGzp/DapHXbmMV3PDqoxc+ewmuT120jFt/1/Kar3uKL16as2/QW3/kErqne4ovXpq7bCrJSK7xHSwbVW3zx2gbrtiJRs9x+oiXz1Vv8AJzOcjts+lm8Ns26bSLL7bLp3uJnPnltGpGhxffjJY7w2lJHvLYJkYHF9wNwhNeWuuW16ddtY1lux06UlteWeuO1Cd90WW7Oa/MFcKR6iy9em/gNyXLHgyy3czd4qnqLvzEYBAqvzR9AbfUWN7y21UBWFgFeG52zfQL0zmsbF8EWn1olrwCtqrc4BajLcvsAKFRvccprMxAZZrm9AHwar20o8l/gtc2s2ySL7zFaYli9xTlAzmvz9BYglzWs3uIeoMRr8wjQsHqLB4C/ymsbZLl9APxNXtsgy+0FoJjl9glQw2sbZLn9ABSy3F4Beue1jYv8Mq8tTu/14lYM121P4rXVHwySYA9hWRMbVAR6BOCzeG1lKW7aJe1KdqpnKgLNtzIp8hReW5TXpx6Sug/psa9O7wHgM3htXclaASDflYxXBKLl8x2tJmRZ77y2Er11Rd/UXcmkDQPJnh1eAMIK2CevDXbmaMVRF4bqrmQsFH76bqSKQA4mGfLhK2APALuiaPKGoyqvTdhuJtlDQWnXAKey3A8C7IqiMb5eOLkrGQvbkCrPbgF647XR4df3xeldyXheqmVVnh3Mop3SfnhtUDFa2jRG3jpdsysZr7ICxfkbPOk4eoJeeG0R1E8Nw3GAo7uSsYDj5SvX75iwAKB7XhvU9rmEKkAZ0tiuZH1E9bINyKrqcYCueW0RCrYUn2YMju1KpgKEvhrvdFvpLVm3ueS1lfXbbrhLdaxaPnlXstE95pPDmZXEewCgQ14b1Ek7JDzUOw5QznKPAoQNvOlu8o8AdMdr63aLz1Q1h11U2ZVsHCA5c7+t8ocAuuK1oUrebmb8CSrVW6YAdgdtUTELuWiV7oLXBiXu2mk1BYDyrmTTTzDsfKCPM1peAvdxXhvUDZ1VU55WxOot+jPVzs23ILJfAlGLXyzktUGpyUNmqKZ2VzLDZx9ePxGthWsH8DFeW5Sjn0u/3cx8F9XsSmZ6ZoYHZI2sdivoZB/gtZWoLvqdGCwAStVbLDp3mNzPw/2k5pRezGsD5zrpdTDvoqFi8W1uTRIfusLGFrZtIa8NdkSZfA4TAGWLbwOQyJLa8Ba2bQmvDTabvMx2tDE15Sy3NcAQKufmFpvN2vPa8Np92+qbnh9Jgyy3xbPvZRO6G5LRU7HltWHrt25HmzZQU8ly2z/BTjY5fBtuNmvHayubPwe2v+SiLqry2pYChA/ZB3Reaal6y7QsbBV7NXOZZ9QMxs8MTQF2m81W85vNjlRvGcpGeQVbghjc23k1A/2ZRmNQlm35pimj40qIeU8BxNYP6u2bNz0pG2jPtOmiffy8vXebdozOjH2WewJg2XQ7ZpqMDgM1pV3Jlo1B6TbC7s/jtm2e14aH3/fVoulZNWVe27IxqMjuP1mR7qF3Msdri3J5iyzLpnWy0q5kD3VRIW5A01ca92ua1wY7NbPy7Y6eoGrxXQBkG8HVuo3gpnhtUIKf19t3CFC0+M4AwufwXqtb9U7w2iLYaEg2P/MAx5pWZEVe24zSdv5SePkkFlJn8RVeW5lXZPi5ubeqrJzldtvKccN3rB9a/D7vXkfF0bJpG9lg8Zkm/aRdB+LuDhpeW1lHu3RB0+aygXLmrNJW/SSFAYkqtsMK2pGl6A5RgGQ/7NDLvdXz2jwNhOuNbfPQ7Mk3x4bsWYqdz9uJeR5+moZgvchr89bKZYNQGZUNe+Gq+B/+C6HNMVnQtJWaMq/NE0AigtfJ6J3FOrP08I3Ou1jMu3sCmEq7kjkeg4MwK39Jzomfb9y0wGvz1Ioq+9jMaK+mxGvzATBVZJ8NkFt8pgjPJfa7mYtO3ojsUCSRRZzJGqvJRQLlzEw54BNtNmxlVFajNJddcLklTfdbKnZ5fGYds/kDAxE2pRjJJvayVmoSi89b6Q/CsYNlso4vZ9X0/wF/1HNTwlwRSwAAAABJRU5ErkJggg=='
}
})
.state('home.order', {
url: '/order',
views: {
'main#': {
templateUrl: 'order.html',
}
},
data: {
displayName: 'ordericon',
displayImg: 'data:image/png;base64,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'
}
})
This is the breadcrumb library have used for dynamic menu
Breadcrumb js:
/**
* uiBreadcrumbs automatic breadcrumbs directive for AngularJS & Angular ui-router.
*
* https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs
*
* Copyright 2014 Michael Bromley <michael#michaelbromley.co.uk>
*/
(function() {
/**
* Config
*/
var moduleName = 'ngBreadCrumb';
var templateUrl = 'ngBreadCrumbTemplate.html';
/**
* Module
*/
var module;
try {
module = angular.module(moduleName);
} catch(err) {
// named module does not exist, so create one
module = angular.module(moduleName, ['ui.router']);
}
module.directive('uiBreadcrumbs', ['$interpolate', '$state', function($interpolate, $state) {
return {
restrict: 'E',
templateUrl: function(elem, attrs) {
return attrs.templateUrl || templateUrl;
},
scope: {
displaynameProperty: '#',
displayimgProperty:'#',
abstractProxyProperty: '#?'
},
link: function(scope) {
scope.breadcrumbs = [];
if ($state.$current.name !== '') {
updateBreadcrumbsArray();
}
scope.$on('$stateChangeSuccess', function() {
updateBreadcrumbsArray();
});
/**
* Start with the current state and traverse up the path to build the
* array of breadcrumbs that can be used in an ng-repeat in the template.
*/
function updateBreadcrumbsArray() {
var workingState;
var displayName;
var displayImg = 'http://www.freeiconspng.com/uploads/home-icon-png-home-icon-31.png'; //Sampriya
var breadcrumbs = [];
var currentState = $state.$current;
while(currentState && currentState.name !== '') {
workingState = getWorkingState(currentState);
if (workingState) {
displayName = getDisplayName(workingState);
displayImg = getDisplayImg(workingState);//Sampriya
if (displayName !== false && !stateAlreadyInBreadcrumbs(workingState, breadcrumbs)) {
if (displayImg !== false) {
breadcrumbs.push({
displayName: displayName,
displayImg: displayImg,
route: workingState.name
});
}
else {
breadcrumbs.push({
displayName: displayName,
route: workingState.name
});
}
}
//Sampriya
//if (displayImg !== false && !stateAlreadyInBreadcrumbs(workingState, breadcrumbs)) {
// breadcrumbs.push({
// displayImg: displayImg,
// route: workingState.name
// });
//}
//=====
}
currentState = currentState.parent;
}
breadcrumbs.reverse();
scope.breadcrumbs = breadcrumbs;
}
/**
* Get the state to put in the breadcrumbs array, taking into account that if the current state is abstract,
* we need to either substitute it with the state named in the `scope.abstractProxyProperty` property, or
* set it to `false` which means this breadcrumb level will be skipped entirely.
* #param currentState
* #returns {*}
*/
function getWorkingState(currentState) {
var proxyStateName;
var workingState = currentState;
if (currentState.abstract === true) {
if (typeof scope.abstractProxyProperty !== 'undefined') {
proxyStateName = getObjectValue(scope.abstractProxyProperty, currentState);
if (proxyStateName) {
workingState = $state.get(proxyStateName);
} else {
workingState = false;
}
} else {
workingState = false;
}
}
return workingState;
}
/**
* Resolve the displayName of the specified state. Take the property specified by the `displayname-property`
* attribute and look up the corresponding property on the state's config object. The specified string can be interpolated against any resolved
* properties on the state config object, by using the usual {{ }} syntax.
* #param currentState
* #returns {*}
*/
function getDisplayName(currentState) {
var interpolationContext;
var propertyReference;
var displayName;
if (!scope.displaynameProperty) {
// if the displayname-property attribute was not specified, default to the state's name
return currentState.name;
}
propertyReference = getObjectValue(scope.displaynameProperty, currentState);
if (propertyReference === false) {
return false;
} else if (typeof propertyReference === 'undefined') {
return currentState.name;
} else {
// use the $interpolate service to handle any bindings in the propertyReference string.
interpolationContext = (typeof currentState.locals !== 'undefined') ? currentState.locals.globals : currentState;
displayName = $interpolate(propertyReference)(interpolationContext);
return displayName;
}
}
//Sampriya
/**
* Resolve the displayImg of the specified state. Take the property specified by the `displayimg-property`
* attribute and look up the corresponding property on the state's config object. The specified string can be interpolated against any resolved
* properties on the state config object, by using the usual {{ }} syntax.
* #param currentState
* #returns {*}
*/
function getDisplayImg(currentState) {
var interpolationContext;
var propertyReference;
var displayImg;
if (!scope.displayimgProperty) {
// if the displayimg-property attribute was not specified, default to the state's name
return currentState.name;
}
propertyReference = getObjectValue(scope.displayimgProperty, currentState);
if (propertyReference === false) {
return false;
} else if (typeof propertyReference === 'undefined') {
return currentState.name;
} else {
// use the $interpolate service to handle any bindings in the propertyReference string.
interpolationContext = (typeof currentState.locals !== 'undefined') ? currentState.locals.globals : currentState;
displayImg = $interpolate(propertyReference)(interpolationContext);
return displayImg;
}
}
//=======
/**
* Given a string of the type 'object.property.property', traverse the given context (eg the current $state object) and return the
* value found at that path.
*
* #param objectPath
* #param context
* #returns {*}
*/
function getObjectValue(objectPath, context) {
var i;
var propertyArray = objectPath.split('.');
var propertyReference = context;
for (i = 0; i < propertyArray.length; i ++) {
if (angular.isDefined(propertyReference[propertyArray[i]])) {
propertyReference = propertyReference[propertyArray[i]];
} else {
// if the specified property was not found, default to the state's name
return undefined;
}
}
return propertyReference;
}
/**
* Check whether the current `state` has already appeared in the current breadcrumbs array. This check is necessary
* when using abstract states that might specify a proxy that is already there in the breadcrumbs.
* #param state
* #param breadcrumbs
* #returns {boolean}
*/
function stateAlreadyInBreadcrumbs(state, breadcrumbs) {
var i;
var alreadyUsed = false;
for(i = 0; i < breadcrumbs.length; i++) {
if (breadcrumbs[i].route === state.name) {
alreadyUsed = true;
}
}
return alreadyUsed;
}
}
};
}]);
})();
In the state router have under the data section added the image and display name
for the particular icon click.
Breadcrumb template:
<ol class="breadcrumb">
<li ng-repeat="crumb in breadcrumbs">
<!--ng-class="{ active: $last }"--><a ui-sref="{{ crumb.route }}" ng-if="!$last"><img ng-src="{{ crumb.displayImg }}" class="responsiveBCrumb" alt="" />{{crumb.displayName}}</a><span ng-show="$last"><img ng-src="{{ crumb.displayImg }}" class="responsiveBCrumb" alt="" />{{crumb.displayName}}</span>
</li>
</ol>
Every template add this for dynamic menu
product.html
<h1>Product</h1>
<div class="note_panel note_panel_yellow">
<div class="note_body note_panel_solid_border">
<h4>Product Page</h4>
<p>Sample Angular Breadcrumb</p>
</div>
</div>
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!
In definition of AngularJS module, [] is a parameter for other depended module on this module.
<script>
var app = angular.module('myApp',[]);
app.controller("myCtrl", function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
});
</script>
My Question is,
Is this parameter [] necessary, because the the following link or example they didn't mentioned [] parameter, but in above example(w3schooles), if we remove '[]' parameter then code will not give correct output see it?
Please see the this link openstack, they are not using [] parameter
var module = angular.module('hz.dashboard.launch-instance');
/**
* #ngdoc service
* #name launchInstanceModel
*
* #description
* This is the M part in MVC design pattern for launch instance
* wizard workflow. It is responsible for providing data to the
* view of each step in launch instance workflow and collecting
* user's input from view for creation of new instance. It is
* also the center point of communication between launch instance
* UI and services API.
*/
module.factory('launchInstanceModel', ['$q',
'cinderAPI',
'glanceAPI',
'keystoneAPI',
'neutronAPI',
'novaAPI',
'novaExtensions',
'securityGroup',
'serviceCatalog',
function ($q,
cinderAPI,
glanceAPI,
keystoneAPI,
neutronAPI,
novaAPI,
novaExtensions,
securityGroup,
serviceCatalog) {
var initPromise,
allNamespacesPromise;
// Constants (const in ES6)
var NON_BOOTABLE_IMAGE_TYPES = ['aki', 'ari'],
SOURCE_TYPE_IMAGE = 'image',
SOURCE_TYPE_SNAPSHOT = 'snapshot',
SOURCE_TYPE_VOLUME = 'volume',
SOURCE_TYPE_VOLUME_SNAPSHOT = 'volume_snapshot';
/**
* #ngdoc model api object
*/
var model = {
initializing: false,
initialized: false,
/**
* #name newInstanceSpec
*
* #description
* A dictionary like object containing specification collected from user's
* input. Its required properties include:
*
* #property {String} name: The new server name.
* #property {String} source_type: The type of source
* Valid options: (image | snapshot | volume | volume_snapshot)
* #property {String} source_id: The ID of the image / volume to use.
* #property {String} flavor_id: The ID of the flavor to use.
*
* Other parameters are accepted as per the underlying novaclient:
* - https://github.com/openstack/python-novaclient/blob/master/novaclient/v2/servers.py#L417
* But may be required additional values as per nova:
* - https://github.com/openstack/horizon/blob/master/openstack_dashboard/api/rest/nova.py#L127
*
* The JS code only needs to set the values below as they are made.
* The createInstance function will map them appropriately.
*/
// see initializeNewInstanceSpec
newInstanceSpec: {},
/**
* cloud service properties, they should be READ-ONLY to all UI controllers
*/
availabilityZones: [],
flavors: [],
allowedBootSources: [],
images: [],
allowCreateVolumeFromImage: false,
arePortProfilesSupported: false,
imageSnapshots: [],
keypairs: [],
metadataDefs: {
flavor: null,
image: null,
volume: null
},
networks: [],
neutronEnabled: false,
novaLimits: {},
profiles: [],
securityGroups: [],
volumeBootable: false,
volumes: [],
volumeSnapshots: [],
/**
* api methods for UI controllers
*/
initialize: initialize,
createInstance: createInstance
};
// Local function.
function initializeNewInstanceSpec(){
model.newInstanceSpec = {
availability_zone: null,
admin_pass: null,
config_drive: false,
user_data: '', // REQUIRED Server Key. Null allowed.
disk_config: 'AUTO',
flavor: null, // REQUIRED
instance_count: 1,
key_pair: [], // REQUIRED Server Key
name: null, // REQUIRED
networks: [],
profile: {},
security_groups: [], // REQUIRED Server Key. May be empty.
source_type: null, // REQUIRED for JS logic (image | snapshot | volume | volume_snapshot)
source: [],
vol_create: false, // REQUIRED for JS logic
vol_device_name: 'vda', // May be null
vol_delete_on_terminate: false,
vol_size: 1
};
}
/**
* #ngdoc method
* #name launchInstanceModel.initialize
* #returns {promise}
*
* #description
* Send request to get all data to initialize the model.
*/
function initialize(deep) {
var deferred, promise;
// Each time opening launch instance wizard, we need to do this, or
// we can call the whole methods `reset` instead of `initialize`.
initializeNewInstanceSpec();
if (model.initializing) {
promise = initPromise;
} else if (model.initialized && !deep) {
deferred = $q.defer();
promise = deferred.promise;
deferred.resolve();
} else {
model.initializing = true;
model.allowedBootSources.length = 0;
promise = $q.all([
getImages(),
novaAPI.getAvailabilityZones().then(onGetAvailabilityZones, noop),
novaAPI.getFlavors(true, true).then(onGetFlavors, noop),
novaAPI.getKeypairs().then(onGetKeypairs, noop),
novaAPI.getLimits().then(onGetNovaLimits, noop),
securityGroup.query().then(onGetSecurityGroups, noop),
serviceCatalog.ifTypeEnabled('network').then(getNetworks, noop),
serviceCatalog.ifTypeEnabled('volume').then(getVolumes, noop)
]);
promise.then(
function() {
model.initializing = false;
model.initialized = true;
// This provides supplemental data non-critical to launching
// an instance. Therefore we load it only if the critical data
// all loads successfully.
getMetadataDefinitions();
},
function () {
model.initializing = false;
model.initialized = false;
}
);
}
return promise;
}
/**
* #ngdoc method
* #name launchInstanceModel.createInstance
* #returns {promise}
*
* #description
* Send request for creating server.
*/
function createInstance() {
var finalSpec = angular.copy(model.newInstanceSpec);
cleanNullProperties();
setFinalSpecBootsource(finalSpec);
setFinalSpecFlavor(finalSpec);
setFinalSpecNetworks(finalSpec);
setFinalSpecKeyPairs(finalSpec);
setFinalSpecSecurityGroups(finalSpec);
return novaAPI.createServer(finalSpec);
}
function cleanNullProperties(finalSpec){
// Initially clean fields that don't have any value.
for (var key in finalSpec) {
if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null) {
delete finalSpec[key];
}
}
}
//
// Local
//
function onGetAvailabilityZones(data) {
model.availabilityZones.length = 0;
push.apply(model.availabilityZones, data.data.items
.filter(function (zone) {
return zone.zoneState && zone.zoneState.available;
})
.map(function (zone) {
return zone.zoneName;
})
);
if(model.availabilityZones.length > 0) {
model.newInstanceSpec.availability_zone = model.availabilityZones[0];
}
}
// Flavors
function onGetFlavors(data) {
model.flavors.length = 0;
push.apply(model.flavors, data.data.items);
}
function setFinalSpecFlavor(finalSpec) {
if ( finalSpec.flavor ) {
finalSpec.flavor_id = finalSpec.flavor.id;
} else {
delete finalSpec.flavor_id;
}
delete finalSpec.flavor;
}
// Keypairs
function onGetKeypairs(data) {
angular.extend(
model.keypairs,
data.data.items.map(function (e) {
e.keypair.id = e.keypair.name;
return e.keypair;
}));
}
function setFinalSpecKeyPairs(finalSpec) {
// Nova only wants the key name. It is a required field, even if None.
if(!finalSpec.key_name && finalSpec.key_pair.length === 1){
finalSpec.key_name = finalSpec.key_pair[0].name;
} else if (!finalSpec.key_name) {
finalSpec.key_name = null;
}
delete finalSpec.key_pair;
}
// Security Groups
function onGetSecurityGroups(data) {
model.securityGroups.length = 0;
push.apply(model.securityGroups, data.data.items);
// set initial default
if (model.newInstanceSpec.security_groups.length === 0 &&
model.securityGroups.length > 0) {
model.securityGroups.forEach(function (securityGroup) {
if (securityGroup.name === 'default') {
model.newInstanceSpec.security_groups.push(securityGroup);
}
});
}
}
function setFinalSpecSecurityGroups(finalSpec) {
// pull out the ids from the security groups objects
var security_group_ids = [];
finalSpec.security_groups.forEach(function(securityGroup){
if(model.neutronEnabled) {
security_group_ids.push(securityGroup.id);
} else {
security_group_ids.push(securityGroup.name);
}
});
finalSpec.security_groups = security_group_ids;
}
// Networks
function getNetworks() {
return neutronAPI.getNetworks().then(onGetNetworks, noop);
}
function onGetNetworks(data) {
model.neutronEnabled = true;
model.networks.length = 0;
push.apply(model.networks, data.data.items);
}
function setFinalSpecNetworks(finalSpec) {
finalSpec.nics = [];
finalSpec.networks.forEach(function (network) {
finalSpec.nics.push(
{
"net-id": network.id,
"v4-fixed-ip": ""
});
});
delete finalSpec.networks;
}
// Boot Source
function getImages(){
return glanceAPI.getImages({status:'active'}).then(onGetImages);
}
function isBootableImageType(image){
// This is a blacklist of images that can not be booted.
// If the image container type is in the blacklist
// The evaluation will result in a 0 or greater index.
return NON_BOOTABLE_IMAGE_TYPES.indexOf(image.container_format) < 0;
}
function onGetImages(data) {
model.images.length = 0;
push.apply(model.images, data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(!image.properties || image.properties.image_type !== 'snapshot');
}));
addAllowedBootSource(model.images, SOURCE_TYPE_IMAGE, gettext('Image'));
model.imageSnapshots.length = 0;
push.apply(model.imageSnapshots,data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(image.properties && image.properties.image_type === 'snapshot');
}));
addAllowedBootSource(model.imageSnapshots, SOURCE_TYPE_SNAPSHOT, gettext('Instance Snapshot'));
}
function getVolumes(){
var volumePromises = [];
// Need to check if Volume service is enabled before getting volumes
model.volumeBootable = true;
addAllowedBootSource(model.volumes, SOURCE_TYPE_VOLUME, gettext('Volume'));
addAllowedBootSource(model.volumeSnapshots, SOURCE_TYPE_VOLUME_SNAPSHOT, gettext('Volume Snapshot'));
volumePromises.push(cinderAPI.getVolumes({ status: 'available', bootable: 1 }).then(onGetVolumes));
volumePromises.push(cinderAPI.getVolumeSnapshots({ status: 'available' }).then(onGetVolumeSnapshots));
// Can only boot image to volume if the Nova extension is enabled.
novaExtensions.ifNameEnabled('BlockDeviceMappingV2Boot')
.then(function(){ model.allowCreateVolumeFromImage = true; });
return $q.all(volumePromises);
}
function onGetVolumes(data) {
model.volumes.length = 0;
push.apply(model.volumes, data.data.items);
}
function onGetVolumeSnapshots(data) {
model.volumeSnapshots.length = 0;
push.apply(model.volumeSnapshots, data.data.items);
}
function addAllowedBootSource(rawTypes, type, label) {
if (rawTypes && rawTypes.length > 0) {
model.allowedBootSources.push({
type: type,
label: label
});
}
}
function setFinalSpecBootsource(finalSpec) {
finalSpec.source_id = finalSpec.source && finalSpec.source[0] && finalSpec.source[0].id;
delete finalSpec.source;
switch (finalSpec.source_type.type) {
case SOURCE_TYPE_IMAGE:
setFinalSpecBootImageToVolume(finalSpec);
break;
case SOURCE_TYPE_SNAPSHOT:
break;
case SOURCE_TYPE_VOLUME:
setFinalSpecBootFromVolumeDevice(finalSpec, 'vol');
break;
case SOURCE_TYPE_VOLUME_SNAPSHOT:
setFinalSpecBootFromVolumeDevice(finalSpec, 'snap');
break;
default:
// error condition
console.log("Unknown source type: " + finalSpec.source_type);
}
// The following are all fields gathered into simple fields by
// steps so that the view can simply bind to simple model attributes
// that are then transformed a single time to Nova's expectation
// at launch time.
delete finalSpec.source_type;
delete finalSpec.vol_create;
delete finalSpec.vol_device_name;
delete finalSpec.vol_delete_on_terminate;
delete finalSpec.vol_size;
}
function setFinalSpecBootImageToVolume(finalSpec){
if(finalSpec.vol_create) {
// Specify null to get Autoselection (not empty string)
var device_name = finalSpec.vol_device_name ? finalSpec.vol_device_name : null;
finalSpec.block_device_mapping_v2 = [];
finalSpec.block_device_mapping_v2.push(
{
'device_name': device_name,
'source_type': SOURCE_TYPE_IMAGE,
'destination_type': SOURCE_TYPE_VOLUME,
'delete_on_termination': finalSpec.vol_delete_on_terminate ? 1 : 0,
'uuid': finalSpec.source_id,
'boot_index': '0',
'volume_size': finalSpec.vol_size
}
);
}
}
function setFinalSpecBootFromVolumeDevice(finalSpec, sourceType) {
finalSpec.block_device_mapping = {};
finalSpec.block_device_mapping[finalSpec.vol_device_name] = [
finalSpec.source_id,
':',
sourceType,
'::',
(finalSpec.vol_delete_on_terminate ? 1 : 0)
].join('');
// Source ID must be empty for API
finalSpec.source_id = '';
}
// Nova Limits
function onGetNovaLimits(data) {
angular.extend(model.novaLimits, data.data);
}
// Metadata Definitions
/**
* Metadata definitions provide supplemental information in detail
* rows and should not slow down any of the other load processes.
* All code should be written to treat metadata definitions as
* optional, because they are never guaranteed to exist.
*/
function getMetadataDefinitions() {
// Metadata definitions often apply to multiple
// resource types. It is optimal to make a single
// request for all desired resource types.
var resourceTypes = {
flavor: 'OS::Nova::Flavor',
image: 'OS::Glance::Image',
volume: 'OS::Cinder::Volumes'
};
angular.forEach(resourceTypes, function (resourceType, key) {
glanceAPI.getNamespaces({
'resource_type': resourceType
}, true)
.then(function (data) {
var namespaces = data.data.items;
// This will ensure that the metaDefs model object remains
// unchanged until metadefs are fully loaded. Otherwise,
// partial results are loaded and can result in some odd
// display behavior.
if(namespaces.length) {
model.metadataDefs[key] = namespaces;
}
});
});
}
return model;
}
]);
})();
Status
The array or [] parameter is needed to specify dependent modules when you declare your own module, so this should only happen once per module.
The second notation, without the parameter is just retrieving the module so you can attach controllers/services/filters/... to it.
Use the array notation for the declaration of your module, use the single parameter notation if you want to add something to it.
For example:
in app.module.js
//You want to make use of the ngRoute module,
//so you have to specify a dependency on it
angular.module('app', ['ngRoute']);
You will only specify the dependencies on your module once, when you declare it.
in main.controller.js
//You want to add a controller to your module, so you want to retrieve your module
angular.module('app').controller('mainCtrl', mainCtrl);
function mainCtrl() { };
Now angular will try to find a module by that name instead of creating one, when it doesn't find one, you'll get some errors, which explains your original question.
You will typically do this every time you want to add something to your module.
Note that you could also achieve this by storing your module in a global variable when you create it and then access the module by that variable when you want to add things to it, however as you probably know, creating global variables is a bad practice.
Facing an error with angular is a bliss because it provides the link to description of the error in the console.
From an example page like that...
When defining a module with no module dependencies, the array of dependencies should be defined and empty.
var myApp = angular.module('myApp', []);
To retrieve a reference to the same module for further configuration, call angular.module without the array argument.
var myApp = angular.module('myApp');
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;
}
});
});