Make switch directive - javascript

I'm trying to pass in a $scope that I want to change the value on.
These are two switch button on the same page that.
View
<div class="input">
<switch state="false" toggle="user.emailNotification">
<flip></flip>
</switch>
</div>
<div ng-click="saveNotifySettings()" class="button fill primary">Vista</div>
Controller
app.controller('notifySettingCtrl', function($scope, Users){
Users.get().$promise.then(function(data) {
$scope.user = data;
console.log($scope.user.emailNotification);
});
$scope.saveNotifySettings = function() {
console.log("PUT the new value for the switch");
};
});
When I press the button the state changes from false to true
First I want to init the state with the existing value from $scope.user.emailNotification and then change it and pass the changes to the controller.
The beginning of the my directive.
switch directive
(function() {
define(['angular', 'app', 'underscore'], function(angular, app, _) {
app.directive('switch', function() {
return {
restrict: 'E',
scope: {
value: '&toggle',
},
link: function(scope, element) {
var x = scope.value();
element.bind('mouseenter', function() {
console.log(x);
});
var $element = angular.element(element),
uniqueId = _.uniqueId('switch-'),
state = $element.attr('state');
state = typeof state === 'undefined' ? false : state;
$element.data('uniqueId',uniqueId)
.attr('state',state)
.attr('alive',true);
$element.on('click.'+uniqueId,function(){
if (state === true) {
$(this).attr('state',false);
state = false;
} else {
$(this).attr('state',true);
state = true;
}
});
}
};
});
});
}).call(this);
I'm very new at creating directives, so any help is well appreciated.
I have spend way to much time at this and it getting frustrating :/
Update:
I have created http://jsfiddle.net/HuLn4/. I have taken the pointers from you and refactored.
TLDR: I'm trying to create directive by sending in the model that I want to change in this case user.emailNotification and when the element is clicked on it changes the value from true/false and false/true and stores it back to the controllers $scope.user so it can be PUT to server and the state attribute is only to tell the look on the switch and for the initialize on the button ( on / off ).

Related

How to handle different content depending on selected element in the same sidebar

Imaging that we have animals table. Each row describes one animal, for example: ID, NAME, TYPE.
Depending on the selected row type, I want show is the sidebar content related to that animal and some user actions.
Content is completely different, it pulls from data from different APIs.
But the sidebar placed always in the same position, same size and styles.
Maybe I'll have common actions for each controller, like -> close sidebar.
If sidebar already opened and user switch to another one, sidebar should change immediately.
How should I design such login with angular ?
I got an idea to define one directive in html for sidebar. And set listener for selected row, after that compile dynamically sidebar directive for selected row, and insert into parent (main) sidebar.
Probably also I need to handle destroy of previous one.
I appreciate if anyone can tell is I'm going the right way... or should I change something ?
function dtSidebarDirective($compile, $mdUtil, $mdSidenav, $log, mxRegistry) {
return {
restrict: 'E',
templateUrl: 'app/components/sidebar/sidebar.html',
controller: function($scope) {
// used to replace sidebar data on the fly without recompile
$scope.refresh = function() { }
$scope.close = function(ev) {
$mdSidenav('right').close()
}
},
scope: true,
link: link
};
function link(scope, element) {
// used to detect switching between the same type of elements
var _activeType;
var _childDirective;
var _childScope;
var _childElement;
var _toggle = $mdUtil.debounce(function() {
$mdSidenav('right')
.toggle()
.then(function() {
scope.isOpen = $mdSidenav('right').isOpen();
$log.debug('toggle right is done');
});
});
var _init = function(type, data) {
// by default open diagram sidebar
switch(type) {
case 'shape':
_childDirective = $compile('<dt-dog-sidebar></dt-dog-sidebar>');
break;
case 'text':
_childDirective = $compile('<dt-cat-sidebar></dt-cat-sidebar>');
break;
default:
_childDirective = $compile('<dt-animal-sidebar></dt-diagram-sidebar>');
}
// initialize child sidebar : element & scope
_activeType = type;
_childScope = scope.$new();
_childScope.data = data;
_childElement = _childDirective(_childScope);
element.find('md-sidenav').append(_childElement);
};
var _isInitialized = function(type) {
var isDefined = angular.isDefined(_childDirective);
return type ? _activeType == type && isDefined : isDefined;
};
var _destroy = function() {
if(_isInitialized()) {
_childScope.$destroy();
_childElement.empty();
_childElement.remove();
}
};
function showSidebar(ev, type, data) {
// lets figure out does we open the same kind of sidebar
if(_isInitialized(type)) {
_childScope.data = data;
_childScope.refresh();
return;
}
// destroy since we gonna replace with new sidebar
_destroy();
_init(type, data);
}
function toggle() {
update();
_toggle();
}
function update(ev) {
// detect which sidebar should be shown now
}
scope.$on('sidebar:toggle', toggle);
scope.$on('sidebar:show', showSidebar);
scope.$on('sidebar:update', update);
}
I manage to get it work with recompiling each time I need to should different sidebar or call refresh of children scope.

Unit test an AngularJS document ready function

I'm writing an AngularJS application and I'm searching for a way to unit test every single aspect.
In this particular case, I need to unit test a custom directive which I've written that represents a control.
The directive can be found here:
var officeButton = angular.module('OfficeButton', []);
officeButton.directive('officeButton', function() {
return {
restrict: 'E',
replace: false,
scope: {
isDefault: '#',
isDisabled: '#',
control: '=',
label: '#'
},
template: '<div class="button-wrapper" data-ng-click="onClick()">' +
'<a href="#" class="button normal-button">' +
'<span>{{label}}</span>' +
'</a>' +
'</div>',
controller: ['$scope', function($scope) {
var event = this;
var api = {
changeLabel: function(label) {
$scope.label = label;
},
enable: function() {
$scope.isDisabled = false;
},
disable: function() {
$scope.isDisabled = true;
},
setAsDefault: function() {
$scope.isDefault = true;
},
removeDefault: function() {
$scope.isDefault = false;
}
};
event.onClick = function() {
if (typeof $scope.control.onClick === 'function') { $scope.control.onClick(); }
};
$.extend($scope.control, api);
function Init() {
if ($scope.isDefault === 'true') { $scope.isDefault = true; }
else { $scope.isDefault = false; }
}
Init();
}],
link: function(scope, element, attributes, controller) {
scope.$watch('isDefault', function(value) {
if (value === 'true' || value) { $('a', element).addClass('button-default'); }
else { $('a', element).removeClass('button-default'); }
});
scope.onClick = function() { controller.onClick(); }
}
}
});
This directive can be called by using the following HTML snippet:
<office-button label="Office Web Controls" control="buttonController"></office-button>
Now, this directive exposes an API which functions such as changeLabel, enable, disable, ....
Now, those functions are not defined on the load of the application, meaning if at the bottom of my HTML I call the following code:
$scope.buttonController.changeLabel('Office Web Controls for Web Applications Directive Demo');
It will throw an error because the changeLabel() method is not defined.
In order to make it function, I need to wrap those calls in an angular.ready function, such as:
angular.element(document).ready(function () {
$scope.buttonController.changeLabel('Office Web Controls for Web Applications Directive Demo');
});
Here's a plunker for your information.
Now, I'm writing unit tests using Jasmine, and here's what I have for the moment:
describe('Office Web Controls for Web Applications - Button Testing.', function() {
// Provides all the required variables to perform Unit Testing against the 'button' directive.
var $scope, element;
var buttonController = {};
// Loads the directive 'OfficeButton' before every test is being executed.
beforeEach(module('OfficeButton'));
// Build the element so that it can be called.
beforeEach(inject(function($rootScope, $compile) {
// Sets the $scope variable so that it can be used in the future.
$scope = $rootScope;
$scope.control = buttonController;
element = angular.element('<office-button control="buttonController"></office-button>');
$compile(element)($scope);
$scope.$digest();
}));
it('Should expose an API with certain functions.', function() {
});
});
Now, in the it function, I would like to test if the $scope.control does expose the API as defined in the directive.
The problem is that the page needs to be ready before the API is available.
Any tought on how to change the code or how to unit test this correctly?
I've found the issue, it was just a wrong configuration on the unit test.
When using this code:
$scope.control = buttonController;
element = angular.element('<office-button control="buttonController"></office-button>');
I must change the element to:
$scope.control = buttonController;
element = angular.element('<office-button control="control"></office-button>');

Update angularjs view when using "Controller as var" syntax

I've a small site that has 3 views, and everytime i change the view, new data is loaded into it (Home, search sombody. Dashboard, a collection of data about them, detail, specific deatils about one item).
I am loading the data like such
<section id="recentSearches" class="frame" data-ng-controller="HomeCtrl as ctrl">
<h1>Recently Searched</h1>
<div data-ng-repeat="name in ctrl.recentSearches | reverse">
<a class="name" href="/#/dashboard/{{name | formatSlug}}" data-ng-bind="name"></a>
</div>
</section>
and just for reference, recentSearches is populated in the controller by a service (this is HomeCtrl) like such:
this.recentSearches = Services.recentSearches.data;
My problem is, every time i switch a view, it doesn't update the previous data in the view, when I reload the page though, it assigns the proper data in the view (recalling the data based on the permalink).
I assume this is happening because the data is not in $scope? When i had the data in scope it updated fine, but i am moving away from storing data in scope completely and this is my last hurtle.
Any help is greatly appreciated and let me know if you need any more info or code. Thanks!
UPDATE: One of my controllers, the other 2 are setup in the same way.
'use strict';
angular.module('myApp.Controller.Home', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/home.html',
pageTitle: 'Home'
});
}])
.controller('HomeCtrl', ['Services', '$rootScope', 'RecentGames', 'Summoners', 'Champions', '$filter',
function(Services, $rootScope, RecentGames, Summoners, Champions, $filter) {
this.slContainer = document.getElementById('summonerLookup');
this.rsContainer = document.getElementById('recentSearches');
this.recentSearches = Services.recentSearches.data;
// Initalize
// -----------------------
this.init = function ( ) {
Services.log('[init][controller] Home');
// Focus on the form
this.slContainer.querySelector('input[name=summonerName]').focus();
// Register Event Listeners
this.events();
// Remove the loading overlay
Services.removeLoading();
};
// Assign Events
// -----------------------
this.events = function ( ) {
// The main form input submit
this.slContainer.addEventListener('submit', this.summonerLookup);
// The recent searches list
this.rsContainer.addEventListener('click', this.loadRecentlySearched.bind(this));
};
// Submit the lookup form
// -----------------------
this.summonerLookup = function ( e, val ) {
if(e){
e.preventDefault();
}
if(!val){
val = $filter('formatSlug')(this.summonerName.value);
}
Summoners.getByName( val ).then(function success(results){ // Grab the summoner
RecentGames.get().then(function success(results){ // Populate the summoners recent games
RecentGames.data = results;
Champions.getUsed();
});
});
};
// Load a recently searched profile
// TODO: load recentsearched from the a tag being clicked, not this.
// -----------------------
this.loadRecentlySearched = function ( e ) {
var el = e.target || e.toElement;
var isButton = el.className.match(/name/g);
// Block event if is not button
if( isButton === null ) { return false; }
// Get summoner information
this.summonerLookup( null, $filter('formatSlug')(el.innerHTML) );
e.preventDefault();
};
return this.init();
}]);

How to make an AngularJS scope variable dependent on media query with EnquireJS?

The goal is to make an Angular scope variable dependent on the screen resolution. I found that the EnquireJS library does just that. The final working result should be that the menu collapses when screen is 480px, based on an isCollapsed variable. (I'm using Angular-ui from bootstrap) Note: I must use Angular.
I can attach the Angular scope variable (isCollapsed) to the Javascript $window and address the $window in EnquireJS, this is what I have.
Create module + controller and attach scope + window
angular.module('PremiumMeat', ['ui.bootstrap']);
angular.module('PremiumMeat').controller('CollapseCtrl', function ($scope, $window) {
$scope.isCollapsed = false;
$window.collapsed = false;
});
Enquire setup, initialize variable on false
// Enquire configuration
var enquireQuery= "screen and (max-width:480px)";
var enquireQueryHandler = {
setup: function () {
$scope.isCollapsed = false;
$window.collapsed = false;
angular.element(document.querySelector('[ng-controller="CollapseCtrl"]')).scope().isCollapsed=false;
},
The isCollapsed variable should be initialized on false and become true when screen size reaches 480px;
I'm trying to address the variable through the $window and both the document object, but none work.
match: function () {
$scope.isCollapsed = true;
$window.collapsed = true;
angular.element(document.querySelector('[ng-controller="CollapseCtrl"]')).scope().isCollapsed=true;
},
unmatch: function () {
$scope.isCollapsed = false;
$window.collapsed = false;
angular.element(document.querySelector('[ng-controller="CollapseCtrl"]')).scope().isCollapsed=false;
}
};
Now the question is where to initialize the enquireJS to make it all work.
enquire.register(enquireQuery, enquireQueryHandler, true);
When I put it in the controller it stops working and outside it, it doesn't overrule.
Final working result should be that the menu collapses when screen is 480px. Note: I must use Angular (angular-ui bootstrap)
I created a service to handle EnquireJS media queries in my Angular application that might be of use to you: https://gist.github.com/schuyberg/034a9982bf8326c19fc9
For your case here, the service would look something like this:
services.factory('max480', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
var max480 = {
watch: function() { enquire.register('screen and (max-width: 480px)', max480handler); },
unwatch: function() { enquire.unregister('screen and (max-width: 480px)'); },
ismatch: function(callback) { $rootScope.$on('match480', callback); },
notmatch: function(callback) { $rootScope.$on('unmatch480', callback); },
};
var max480handler = {
match: function() {
$timeout(function(){
$rootScope.$emit('match480');
});
},
unmatch: function(){
$rootScope.$emit('unmatch480');
}
};
return max480;
}]);
Then, inject the service in your controller, and use the following to register the enquire listener, and act on changes:
// this setup can live in your controller
$scope.isCollapsed = false;
$window.collapsed = false;
angular.element(document.querySelector('[ng controller="CollapseCtrl"]')).scope().isCollapsed=false;
// use the service to listen for, and react to changes
max480.watch();
max480.ismatch(function(){
$scope.isCollapsed = true;
$window.collapsed = true;
angular.element(document.querySelector('[ng-controller="CollapseCtrl"]')).scope().isCollapsed=true;
});
max480.notmatch(function(){
$scope.isCollapsed = false;
$window.collapsed = false;
angular.element(document.querySelector('[ng-controller="CollapseCtrl"]')).scope().isCollapsed=false;
});
This service has the advantage of being reusable throughout your application. You can also use max480.unwatch(); to unregister the listener if you need to.

angularjs show doesn't work

I have html button
<button id="postChanges" ng-show="changes.available" data-ng-click="postChanges()">Save</button>
and controller for this view
myApp.controller('myController', ['$scope', function ($scope) {
var changes = {
available : false
}
$scope.postChanges = function () {
console.log('changes before: ' + changes.available);
if (changes.available) {
changes.available = false;
}
else {
changes.available = true;
}
console.log('changes after: ' + changes.available);
}
}]);
I want to make button visible if changes.available are true and hide it when it is false but it doesn't work. I also tried with just boolean value changes = true / changes = false, data-ng-show instead of ng-show '!' before value (ng-show="!changes.available" or ng-show="!changes") but none of this solutions worked. I'm logging changes value in console and it seems it is ok so I suppose it is a problem with button attribute but I have no clue why :(
Changes.available needs to be on $scope ...
var changes = {
available : false
};
... to ...
$scope.changes = {
available : false
};
This should do it. The ng-show on the button needs to be able to see the variable.
should be
$scope.changes = {
available : false
}
Otherwise it is not visible for the ng-show directive to use.

Categories