How to append directives based on conditions - javascript

I have 2 different directives. The first one is returns an iframe with a live video, and the second returns an iframe with a recent video.
The condition is: if live content is present, append live directive, else append recent video directive.
Ive tried with normal html instead of the directives and it works, but when i put the directive element, unfortunately doesnt work.
WORKING
controller.js
function isLiveOn() {
var liveIframe = '<h2>LIVE</h2>';
var videoIframe = '<h2>VIDEO</h2>';
if (vm.live.items[0] != undefined) {
$('.iframe-container').append(liveIframe);
} else {
$('.iframe-container').append(videoIframe);
}
};
NOT WORKING
controller.js
function isLiveOn() {
var liveIframe = '<live-iframe live="vm.live.items[0]"></live-iframe>';
var videoIframe = '<last-video video="vm.activity.items[0]"></last-video>';
if (vm.live.items[0] != undefined) {
$('.iframe-container').append(liveIframe);
} else {
$('.iframe-container').append(videoIframe);
}
};
Each directive has its own html and js file.
Something like that:
directive.html
<div class="live">
<iframe ng-src="{{getIframeSrc(live.id.videoId)}}"></iframe>
</div>
<div class="live-description">
<h4>{{live.snippet.title}}</h4>
</div>
directive.js
app.directive('live', live);
live.$inject = ['$window'];
function live($window) {
var directive = {
link: link,
restrict: 'EA',
templateUrl: 'path',
scope: {
live: '='
}
};
return directive;
function link(scope, element, attrs) {
scope.getIframeSrc = function(id) {
return 'https://www.youtube.com/embed/' + id;
};
}
}
So im thinking its some problem with the directives that im probably missing.
Any help will be appreciated!

Instead of handling the logic in the controller you can control it in UI as it will be easier.
-----Other Html Codes-----
<live-iframe ng-if="vm.live.items[0]" live="vm.live.items[0]"></live-iframe>
<last-video ng-if="!vm.live.items[0]" video="vm.activity.items[0]"></last-video>
-----Other Html Codes-----
And you can remove following lines of code from the controller
var liveIframe = '<live-iframe live="vm.live.items[0]"></live-iframe>';
var videoIframe = '<last-video video="vm.activity.items[0]"></last-video>';
if (vm.live.items[0] != undefined) {
$('.iframe-container').append(liveIframe);
} else {
$('.iframe-container').append(videoIframe);
}

Related

Using different controllers with custom directive in AngularJS?

I have created a search box which is being used on two different views, one is for searching jobs and the other is for searching companies. I have made two separate controllers for both and separate services as well.
Here is the html for the searchbox -
<span class="searchButton"><i class="fa fa-search fa-2x"></i></span>
<input ng-change="companies.search()"
ng-model="companies.searchTerm"
ng-keydown="companies.deleteTerm($event)"
type="text" id="search-box"
style="width: 0px; visibility:hidden;"/>
Here is a script i am using for styling it -
<script type="text/javascript">
var toggleVar = true;
$('.searchButton').on('click', function() {
if(toggleVar) {
$('.searchButton').animate({right: '210px'}, 400);
$('#search-box').css("visibility", "visible");
setTimeout(function() {
$('.searchButton').css("color", "#444444");
}, 200);
$('#search-box').animate({ width: 185 }, 400).focus();
toggleVar = false;
}
else {
$('#search-box').animate({ width: 0 }, 400);
$('.searchButton').animate({right: '25px'}, 400);
setTimeout(function() {
$('.searchButton').css("color", "#eeeeee");
}, 300);
toggleVar = true;
}
});
$('#search-box').focusout(function() {
if(!toggleVar) {
$('#search-box').animate({ width: 0 }, 400);
$('.searchButton').animate({right: '25px'}, 400);
setTimeout(function() {
$('.searchButton').css("color", "#eeeeee");
}, 300);
toggleVar = true;
}
});
</script>
Controller -
angular.module('jobSeekerApp')
.controller('CompaniesallCtrl', ['getAllCompanies', function (companiesService) {
var ctrl = this;
var count;
ctrl.pageNumber = 1;
ctrl.searchPageNumber = 1;
ctrl.isSearching = false;
ctrl.searchTerm = "";
// Initial page load
companiesService.getCompanies(ctrl.pageNumber)
.then(function(response) {
ctrl.companiesList = response.data.results;
count = response.data.count;
checkCount();
}, function(error) {
console.log(error);
});
// User clicks next button
ctrl.getNext = function() {
// If search is not being used
if(ctrl.searchTerm === "" && ctrl.isSearching === false) {
ctrl.pageNumber = ctrl.pageNumber + 1;
companiesService.getCompanies(ctrl.pageNumber)
.then(function(response) {
ctrl.companiesList = ctrl.companiesList.concat(response.data.results);
checkCount();
}, function(error) {
console.log(error);
});
}
// If search is being used
else {
ctrl.searchPageNumber = ctrl.searchPageNumber + 1;
companiesService.searchCompany(ctrl.searchPageNumber, ctrl.searchTerm)
.then(function(response) {
ctrl.companiesList = ctrl.companiesList.concat(response.data.results);
checkCount();
}, function(error) {
console.log(error);
});
}
};
// User backspaces to delete search term
ctrl.deleteTerm = function (event) {
if(event.keyCode === 8) {
ctrl.searchTermLen = ctrl.searchTermLen - 1;
}
// If search box is empty
ctrl.isSearching = ctrl.searchTermLen !== 0;
};
// User clicks search button
ctrl.search = function() {
ctrl.searchTermLen = ctrl.searchTerm.length;
// If search box is empty, show normal results
if(ctrl.searchTerm === "" && ctrl.isSearching === false) {
ctrl.pageNumber = 1;
companiesService.getCompanies(ctrl.pageNumber)
.then(function(response) {
ctrl.companiesList = response.data.results;
count = response.data.count;
checkCount();
}, function(error) {
console.log(error);
});
}
// If search box is not empty, search the input
else {
ctrl.isSearching = true;
ctrl.searchPageNumber = 1;
companiesService.searchCompany(ctrl.searchPageNumber, ctrl.searchTerm)
.then(function(response) {
ctrl.companiesList = response.data.results;
count = response.data.count;
checkCount();
}, function(error) {
console.log(error);
});
}
};
// Function to hide and show next button
function checkCount() {
console.log(count);
$(".nextButton").toggle(count > 10);
count = count - 10;
}
}]);
I am trying to make a directive for this, since all this code is being repeated for the both the views. But how do I make the directive interact with different controllers. And how do i make this part ng-change="companies.search()" ng-model="companies.searchTerm" ng-keydown="companies.deleteTerm($event)" not dependent on the controllers.
I am new to angular and am not sure if this is the right approach or should i let the keep the code separate? Please help.
Server-Side search logic makes it simple
If it is possible that your search logic resides on the server and searching jobs or companies could be distinguished by simply setting a query variable in the URL, then it easy. You could use 1 search directive with an attribute to say which module to search and include this in your HTTP request.
Client-Side search logic slightly more angularjs
If you need different client-side logic for each type of search, consider this approach where there is 1 common search directive, plus 1 directive for each customized search.
the common search directive controls view + common search functionality
a search-companies directive that is restrict: 'A' and require: 'search' and performs functions specific to the company search
a search-jobs directive that is also restrict: 'A' and require: 'search' and performs functions specific to the job search
The concept is that the custom search directives will provide their controller/api object to the common search directive. The common search directive handles the view-controller interaction and calls the provided API functions for customized search functionality.
In Code, this could look something like:
angular.module('SearchDemo', [])
.directive('search', function(){
return {
restrict: 'E',
templateUrl: '/templates/search.tpl.html',
controller: ['$scope', function($scope){
$scope.results = [];
this.setSearchAPI = function(searchAPI){
this.api = searchAPI;
};
$scope.doSearch = function(query){
$scope.results.length = 0;
// here we call one of the custom controller functions
if(this.api && angular.isFunction(this.api.getResults)){
var results = this.api.getResults(query);
// append the results onto $scope.results
// without creating a new array
$scope.results.push.apply($scope.results, results);
}
};
}]
};
})
.directive('searchCompanies', function(){
return {
restrict: 'A',
require: ['search', 'searchCompanies'],
link: function(scope, elem, attr, Ctrl){
// here we pass the custom search-companies controller
// to the common search controller
Ctrl[0].setSearchAPI(Ctrl[1]);
},
controller: ['$scope', function($scope){
// you need to design your common search API and
// implement the custom versions of those functions here
// example:
this.getResults = function(query){
// TODO: load the results for company search
};
}]
};
})
.directive('searchJobs', function(){
return {
restrict: 'A',
require: ['search', 'searchJobs'],
link: function(scope, elem, attr, Ctrl){
// here we pass the custom search-jobs controller
// to the common search controller
Ctrl[0].setSearchAPI(Ctrl[1]);
},
controller: ['$scope', function($scope){
// you need to design your common search API and
// implement the custom versions of those functions here
// example:
this.getResults = function(query){
// TODO: load the results for job search
};
}]
};
});
And using it in template would look like:
<search search-companies></search>
and
<search search-jobs></search>
Multiple searches on one directive
This concept could be easily expanded if you need to have one search directive that searches both companies and jobs.
The change would be to turn the search controller's this.api into an array.
angular.module('SearchDemo', [])
.directive('search', function(){
return {
restrict: 'E',
templateUrl: '/templates/search.tpl.html',
controller: ['$scope', function($scope){
$scope.results = [];
// this.api is now an array and can support
// multiple custom search controllers
this.api = [];
this.addSearchAPI = function(searchAPI){
if(this.api.indexOf(searchAPI) == -1){
this.api.push(searchAPI);
}
};
$scope.doSearch = function(query){
$scope.results.length = 0;
// here we call each of the custom controller functions
for(var i=0; i < this.api.length; i++){
var api = this.api[i];
if(angular.isFunction(api.getResults)){
var results = api.getResults(query);
$scope.results.push.apply($scope.results, results);
}
}
};
}]
};
})
.directive('searchCompanies', function(){
return {
restrict: 'A',
require: ['search', 'searchCompanies'],
link: function(scope, elem, attr, Ctrl){
// here we pass the custom search-companies controller
// to the common search controller
Ctrl[0].addSearchAPI(Ctrl[1]);
},
controller: ['$scope', function($scope){
// you need to design your common search API and
// implement the custom versions of those functions here
// example:
this.getResults = function(query){
// TODO: load the results for company search
};
}]
};
})
.directive('searchJobs', function(){
return {
restrict: 'A',
require: ['search', 'searchJobs'],
link: function(scope, elem, attr, Ctrl){
// here we pass the custom search-jobs controller
// to the common search controller
Ctrl[0].addSearchAPI(Ctrl[1]);
},
controller: ['$scope', function($scope){
// you need to design your common search API and
// implement the custom versions of those functions here
// example:
this.getResults = function(query){
// TODO: load the results for job search
};
}]
};
});
And using it in template would look like:
<search search-companies search-jobs></search>
You will have to pass your data source or service to the directive and bind the events from there.
<body ng-app="customSearchDirective">
<div ng-controller="Controller">
<input type="text" placeholder="Search a Company" data-custom-search data-source="companies" />
<input type="text" placeholder="Search for People" data-custom-search data-source="people" />
<hr>
Searching In: {{ searchSource }}
<br/>
Search Result is At: {{ results }}
</div>
</body>
In this sample I am using data-source to pass an array but you can use a service of course.
Then your directive should use the scope attribute to assign what you passed as parameter in source to the scope of the directive.
You will have the input that is using the directive in the elem parameter to bind all the parameters your desire.
(function(angular) {
'use strict';
angular.module('customSearchDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.companies = ['Microsoft', 'ID Software', 'Tesla'];
$scope.people = ['Gill Bates', 'Cohn Jarmack', 'Melon Musk'];
$scope.results = [];
$scope.searchSource = [];
}])
.directive('customSearch', [function() {
function link(scope, element, attrs) {
element.on("change", function(e) {
var searchTerm = e.target.value;
scope.$parent.$apply(function() {
scope.$parent.searchSource = scope.source;
scope.$parent.results = scope.source.indexOf(searchTerm);
});
});
}
return {
scope: {
source: '='
},
link: link
};
}]);
})(window.angular);
Using scope.$parent feels a bit hacky I know and limits the use of this directive to be a direct child of the controller but I think it's a good way to get you started.
You can try it out: https://plnkr.co/edit/A3jzjek6hyjK4Btk34Vc?p=preview
Just a couple of notes from the example.
The change event work after you remove focus from the text box (not while you're typing
You will have to search the exact string to get a match
Hope it helps.

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

Page not scrollable with ng-include="function()" - The code is not in use anymore

Important Edit
The problem doesn't occur with an ng-hide we removed that code for a bootstrap collapse, but it still occurs. My next guess is the following piece of code
<div ng-include="getTemplateUrl()"></div>
This is the whole directive:
stuffModule.directive('stuffDirective', function ($compile) {
var oldId = undefined;
return {
restrict: 'E',
scope: {
model: '='
},
link: function (scope, elem, attrs) {
scope.$watch(function (scope) {
if (oldId !== scope.model.key) {
oldId = scope.model.key;
return true;
}
return false;
}, function (newValue, oldValue) {
if (scope.model.someswitch) {
switch (scope.model.someswitch) {
case 'condition1':
scope.getTemplateUrl = function () {
return 'condition1.html';
}
break;
case 'condition2':
case 'condition3':
scope.getTemplateUrl = function () {
return 'condition23.html';
}
break;
default:
break;
}
} else {
scope.getTemplateUrl = function () {
return 'default.html';
}
}
});
},
template: '<div ng-include="getTemplateUrl()"></div>'
};
});
Just a short clarification, it is literally not possible to scroll with the mouse, but you can easily tab through the fields.
PS: It only happens in Internet Explorer 11, that is the version our customer is using. In Firefox I don't have that problem.
We replaced the code
Because there is an important presentation tomorrow and a missing scrollbar is something like a really big issue, we decided to remove the piece of code and replace it with just normal routing.
Thanks to all commentors :)
Original question with ng-hide
I have a simple page, where I hide a part with ng-hide. When ng-hide turns false the part gets shown, but randomly the page is not scrollable until I reload the whole page.
If it helps, the data which turn ng-hide to false come from an AJAX request.
EDIT 1 - not relevent anymore
Here is the code which does the HTTP requests
this.getCall = function (url) {
var dfd = $q.defer();
$rootScope.loading = true;
$rootScope.loadingError = false;
$rootScope.progressActive = true;
$rootScope.loadingClass = "progress-bar-info";
$http.get('http://localhost/something', {
cache: true
}).success(function (data) {
$rootScope.loadingClass = "progress-bar-success";
$rootScope.progressActive = false;
$timeout(function () {
$rootScope.loading = false;
}, 500);
dfd.resolve(data);
}).error(function (data, status, headers) {
$rootScope.loading = false;
$rootScope.loadingError = true;
$rootScope.progressActive = false;
$rootScope.loadingClass = "progress-bar-danger";
console.error(data);
dfd.reject(JSON.stringify(data));
});
return dfd.promise;
};
The properties on $routescope are there to show a simple progress bar for every HTTP request.
Many things are weird in your code. $scope.watch callback will be executed when the first function will return a result that is different than the last time it was executed. You will certainly not obtain the expected behavior with what you have: instead simply watch for model.key
Another problem is the way you redefine getTemplateUrl: you should not redefine a function, but change what it returns, as pinted out by #New Dev in a comment.
Fixed directive:
link: function (scope, elem, attrs) {
// Or simply bind templateUrl in your ng-include
scope.getTemplateUrl = function() {
return scope.templateUrl;
}
scope.$watch('model.key', function (newValue) {
if (scope.model.someswitch) {
switch (scope.model.someswitch) {
case 'condition1':
scope.templateUrl = 'condition1.html';
break;
case 'condition2':
case 'condition3':
scope.templateUrl = 'condition23.html';
break;
default:
break;
}
} else {
scope.templateUrl = 'default.html';
}
});
}
Now your scrolling issue has probably nothing to do with that. If the template is right but the scrolling wrong, you should investigate as to what is causing that specific issue. For us to help, we need a way to reproduce or understand the issue.
It could have to do with your ng-include being empty until your watch triggers. You can try using an ng-if, as this will only include your element in the DOM when the ng-if expression is true.
template: '<div ng-if="!whatever you had in your ng-hide" ng-include="getTemplateUrl()"></div>'
I found the solution and it didn't had anything to do with ng-include. The problem was, we use a bootstrap modal that we open like this:
$('#modal').modal('show');
But it does not hide properly, the result is that the body keeps the class modal-open that causes that the scrolling doesn't work anymore.
Thanks to everybody who helped and invested time.

Why is Custom Directive conflicting with ui-sref?

Here is the directive:
directive('cgHasPermissions', ['$animate', '$rootScope', 'PermissionService', 'PERMISSION', '$compile', function ($animate, $rootScope, PermissionService, PERMISSION, $compile) {
return {
multiElement: true,
transclude: 'element',
restrict: 'A',
$$tlb: true,
link: function ($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
var unregister = $scope.$watch('user', function (newValue, oldValue) {
var value = $attr.cgHasPermissions;
var needsPermissions = value || ""
if($rootScope.user && $rootScope.permissions) {
unregister()
}
needsPermissions = value.replace(/\s+/g,"").split(",");
needsPermissions = _.map(needsPermissions, function(perm){
return PERMISSION[perm];
})
var user = $rootScope.user;
if(!needsPermissions || PermissionService.hasPermissions(needsPermissions)){
if (!childScope) {
$transclude (function (clone, newScope) {
childScope = newScope;
clone [clone.length++] = document.createComment (' end cgHasPermissions: ' + $attr.cgHasPermissions + ' ');
block = {
clone: clone
}
$animate.enter(clone, $element.parent (), $element);
});
}
} else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes (block.clone);
$animate.leave (previousElements).then(function () {
previousElements = null;
});
block = null;
}
}
})
}
};
}])
Here is the HTML
<a ui-sref="addStudentForm" cg-has-permissions="canAddNewStudent,canViewStudent">+</a>
There is no problem in the logic of how the permissions work. I have verified that. whenever I remove the directive form HTML, ui-sref works just fine but when I add my directive, the ui-sref probably doesn't get executed and href attributed is not added at all.
I tried an ng-click, which also doesn't work.
What is in this directive that is not letting other directives get executed ?
What I have done is, take the source code of ng-if and create my own directive with an extra condition of permission check but ng-if does work with other elements what's wrong here ?

Object html tag data attribute not working with angularjs binding in IE11

I got any application where I need to display file from urls I got in database. Now this file can be an image and it can be a pdf. So I need to set some binding dynamically. I looked on internet and object tag looked promising but it is not working in IE11. It is working fine in Chrome and Firefox. SO that is why I am asking here for help.
I have created a directive just to make sure If we have to do any dom manipulation. Here goes my directive code.
mainApp.directive("displayFile", function () {
return {
restrict: 'AE', // only activate on element attribute
scope: {
displayFile: "=",
fileType:"="
},
link: function (scope, elem, attrs) {
scope.filePath = "";
var element = angular.element(elem);
// observe the other value and re-validate on change
scope.$watch('displayFile', function (val) {
if (val !== "") {
scope.filePath = val;
scope.type="application/"+ fileType;
//element.attr("data", scope.filePath)
}
});
},
template: '<object data="{{filePath}}" type="{{type}}">'
}
});
My html for directive
<div data-display-pdf="fileUrl" file-type="type"></div>
Attaching an image also for IE and Chrome/FF output
Above image is a comparison between IE and FF
Final cut of directive which is working on IE11, Chrome and Firefox
use it like
<div data-display-file="fileObject"></div>
where fileObject is like
$scope.fileObject = {
fileUrl: "",
type: ""
}
mainApp.directive("displayFile", function () {
var updateElem = function (element) {
return function (displayFile) {
element.empty();
var objectElem = {}
if (displayFile && displayFile.type !== "") {
if (displayFile.type === "pdf") {
objectElem = angular.element(document.createElement("object"));
objectElem.attr("data", displayFile.fileUrl);
objectElem.attr("type", "application/pdf");
}
else {
objectElem = angular.element(document.createElement("img"));
objectElem.attr("src", displayFile.fileUrl);
}
}
element.append(objectElem);
};
};
return {
restrict: "EA",
scope: {
displayFile: "="
},
link: function (scope, element) {
scope.$watch("displayFile", updateElem (element));
}
};
});

Categories