how to add a fonticons in breadcrumb with routing - angularjs

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,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'
}
})
.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>

Related

Is [] parameter in the AngularJS module definition compulsory?

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

AngularJs reflect changes in current browser tab to other tab

Using this code, does angularjs supports binding that will also reflects the changes in the current tab you're working with into the other tab
<input type="text" ng-model="name"><span ng-bind="name"></span>
No, not using just that code.
However, I just wrote a nice directive for you to use with ng-model and ng-bind (does not work with just {{ inline expressions }}, though).
Here it is in action
And here is the code:
/**
* sync-between-tabs directive.
* Use in conjunction with ng-model or ng-bind to synchronise the contents
* between tabs. The value is synced using localStorage, so each thing to sync
* needs a unique key. Specify the key using the sync-between-tabs attribute
* value, or leave blank to use the ng-model or ng-bind attributes.
* Example usage:
* <input ng-model="some.thing" sync-between-tabs></input>
* uses the key "some.thing"
* <input ng-model="some.thing" sync-between-tabs="UNIQUEKEY25"></input>
* uses the key "UNIQUEKEY25"
* <span ng-bind="some.other.thing" sync-between-tabs></span>
* uses the key "some.other.thing"
* <span ng-bind="name" sync-between-tabs="UNIQUE_KEY_12"></span>
* uses the key "UNIQUE_KEY_12"
*/
app.directive('syncBetweenTabs', ['$window', '$parse',
function($window, $parse) {
var callbacks = {}, keysToWatch = [];
var localStorage = {
key: function(key) {
return '__syncValue_' + (key || '').replace('__syncValue_', '');
},
getItem: function(key) {
return $window.localStorage.getItem(localStorage.key(key));
},
setItem: function(key, val) {
$window.localStorage.setItem(localStorage.key(key), val);
},
onItemChange: function(key, callback) {
key = localStorage.key(key);
var keyAlreadyExists = false;
if (keysToWatch.indexOf(key) < 0) {
keysToWatch.push(key);
callbacks[key] = [callback];
} else {
callbacks[key].push(callback);
keyAlreadyExists = true;
}
return function deregister() {
if (!keyAlreadyExists)
keysToWatch = without(keysToWatch, key);
callbacks[key] = without(callbacks[key], callback);
if (callbacks.length === 0)
delete callbacks[key];
};
}
};
function without(arr, value) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] !== value)
newArr.push(arr[i]);
}
return newArr;
}
if ($window.addEventListener) {
$window.addEventListener("storage", handle_storage, false);
} else {
$window.attachEvent("onstorage", handle_storage);
}
function handle_storage(e) {
if (!e) e = $window.event;
if (callbacks[e.key])
angular.forEach(callbacks[e.key], function(callback) {
callback(e.newValue);
});
}
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elem, attrs, ngModelCtrl) {
var key = attrs['syncBetweenTabs'],
mode = 'unknown',
dereg, ngBindExpr
if (ngModelCtrl) {
mode = 'ngModel';
if (!key)
key = attrs['ngModel'];
} else if (attrs['ngBind']) {
mode = 'ngBind';
if (!key)
key = attrs['ngBind'];
} else {
throw new Error('sync-between-tabs only works for ng-model and ng-bind at present');
}
if (mode == 'ngModel') {
ngModelCtrl.$viewChangeListeners.push(function() {
localStorage.setItem(key, ngModelCtrl.$viewValue);
});
var currentValue = localStorage.getItem(key);
if (currentValue && currentValue !== ngModelCtrl.$viewValue) {
ngModelCtrl.$setViewValue(currentValue);
ngModelCtrl.$render();
}
dereg = localStorage.onItemChange(key, function(value) {
ngModelCtrl.$setViewValue(value);
ngModelCtrl.$render();
});
} else {
ngBindExpr = $parse(attrs['ngBind']);
dereg = localStorage.onItemChange(key, function(value) {
ngBindExpr.assign(scope, value);
scope.$digest();
});
}
scope.$on('$destroy', dereg);
}
}
}
]);
assuming both tabs have the same site loaded under the same domain, you can use local/session storage or cookies to post the shared data and make it available to any tab, the downside you have to constantly check for changes. but it should work

Remove invalid values . Backbone validate()

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

Trying to not use watchers in my controllers

So, i'm trying to not use watcher in my controller, but, how could i get a value when it changes?
here is my Controller(MyController) using a listener from my Service:
/**
* #param Int index
*/
myService.listener.setDrawAttentionIndex = function (idTask) {
if (lodash.isNumber(idTask) && !lodash.isNaN(idTask)) {
if ($scope.task.idFromTask === idTask) {
$scope.goal.selectedDrawAttention = true;
} else {
$scope.goal.selectedDrawAttention = false;
}
}
};
and here is my Service:
/**
* #param Int index
*/
this.setDrawAttention = function (idTask) {
if (lodash.isNumber(idTask) && !lodash.isNaN(idTask)) {
drawAttentionIndex = idTask;
that.listener.setDrawAttentionIndex(idTask);
}
};
and my possible watcher:
$scope.$watch('myService.getDrawAttention()', function (index) {
if (lodash.isNumber(index) && !lodash.isNaN(index)) {
if ($scope.task.idFromTask === index) {
$scope.goal.selectedDrawAttention = true;
} else {
$scope.goal.selectedDrawAttention = false;
}
}
});
What's better to use? I don't want use watcher in my controller because it's difficult to test, but, i don't know how i could get the value when it changes... It's a good idea to put a listener from my Service, like i did above? And what happens with my $scope variable inside my service listener?

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