Angular is always updating and bringing new features. Angular CLI has also been updated (currently at version 6.x). With latest version it also updated few parameters when running tests.
Angular is always updating and bringing new features. Angular CLI has also been updated (currently at version 6.x). With latest version it also updated few parameters when running tests.
AngularJS is an amazing framework. Together with jQuery and jQuery UI is a killer combo. But sometimes it’s really difficult to make them work together.
Imagine we have a box (div) and inside some elements that we can drag around.
1 2 3 4 5 6 |
<div id="items" > <span>drag me 1</span> <span>drag me 2</span> <span>drag me 3</span> <span>drag me ...</span> </div> |
We will attach the jQuery UI draggable inside of the directive that we added to html (items-drag)
1 2 3 4 5 6 |
<div id="items" items-drag> <span>drag me 1</span> <span>drag me 2</span> <span>drag me 3</span> <span>drag me ...</span> </div> |
and create an app with a directive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var app = angular.module('app', []); app.directive('itemsDrag', function() { return { link: function(scope, element, attrs) { // element == $('#items') element.find('span').draggable(); scope.$on('$destroy', function() { element.find('span').off('**'); }); } }; }); |
This works, but it’s totally unrealistic. In real life we probably load items from somewhere and populate the div. So let’s try that.
We add the controller ItemsController to the HTML with ng-repeat
1 2 3 4 5 |
<div ng-controller="ItemsController"> <div id="items" items-drag> <span ng-repeat="item in items">drag me {{ item }}</span> </div> </div> |
and add controller to our app.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var app = angular.module('app', []); app.controller('ItemsController', function($scope, $timeout) { $scope.items = []; // we will simulate loading items from the server with timeout $timeout(function() { $scope.items = [1, 2, 3, 4, 5, 6]; }, 4000); }); app.directive('itemsDrag', function() { return { link: function(scope, element, attrs) { element.find('span').draggable(); scope.$on('$destroy', function() { element.find('span').off('**'); }); } }; }); |
This will NOT work. Because when directive is loaded, it will find all spans and attach draggable to them. But because items are empty, it won’t find any spans. When they are loaded from the server ($timeout executes), ng-repeat will repeat and show items, but draggable will not be attached.
We can solve this by adding $watch and watching when items update and attach draggable. Let’s just update our directive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
app.directive('itemsDrag', function() { return { link: function(scope, element, attrs) { scope.$watch('items', function(items) { // items changed, let's reattach again element.find('span').off('**').draggable(); scope.$on('$destroy', function() { element.find('span').off('**'); }); }); } }; }); |
This works. Great. But actually there is a big problem. When ng-repeat is adding the elements into the DOM, $watch method is fired and draggable is attached to items. Problem is that this happens during ng-repeat so draggable is not attached to all elements. What now?
We need to somehow wait for ng-repeat to finish and that all elements/items are loaded into DOM. Based on my research, there is no bulletproof way. Some suggest to use timeout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
app.directive('itemsDrag', function() { return { link: function(scope, element, attrs) { scope.$watch('items', function(items) { // attach draggable when all elements are loaded into DOM // (hopefully) $timeout(function() { element.find('span').off('**').draggable(); }, 500); scope.$on('$destroy', function() { element.find('span').off('**'); }); }); } }; }); |
This solution has one big problem. We cannot never set the right timeout time. If we set too small, it won’t work if we have a long list of items. If we set too large, then we can impact the user experience.
The working solution is actually really simple and works for small or large lists of items without impacting the user experience.
1 2 3 4 5 |
<div ng-controller="ItemsController"> <div id="items"> <span ng-repeat="item in items" items-drag>drag me {{ item }}</span> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var app = angular.module('app', []); app.controller('ItemsController', function($scope, $timeout) { $scope.items = []; // we will simulate loading items from the server with timeout $timeout(function() { $scope.items = [1, 2, 3, 4, 5, 6] }, 4000); }); app.directive('itemsDrag', function() { return { link: function(scope, element, attrs) { element.draggable(); scope.$on('$destroy', function() { element.off('**'); }); } }; }); |
We updated the directive’s element. We don’t attach directive to div#items anymore, but to each span. When each element/item is added to DOM, directive is fired and attaches draggable. So there is no timeouts or watching if $scope.items changed.
For me, this is the best way to combine AngularJS with jQuery UI – Draggable. Of course it also works for any plugin.
While working on my Phonegap project I wanted to load initial data into SQLite. One option is to download it from the internet, but it’s always annoying to notify the user to turn on internet and to wait for all the files to download (especially if you have to download 50 images).
The other option is to load local data. Problem is that identifying the right path to local file can be tricky. But the solution is actually easy.
1 2 3 4 5 |
var path = window.location.href.replace('index.html', ''); $.getJSON(path + "fixtures/initial.json", function(data) { // do something with data }); |
All we need is to get current location, remove index.html to get root folder, append it to path of JSON file and voila.
Imagine you have an array of numbers.
1 |
var numbers = [1, 2, 3, 5, 6, 20]; |
Goal is to get a random element of an array. There are several solutions posted on stackoverflow.com. But most of them are just too complicated, so I created my solution.
1 2 3 4 5 |
Array.prototype.random = function() { var index = Math.floor(Math.random() * (this.length)); return this[index]; }; |
I have extend Array to add function random. What is does it uses random number and length of array to create a random index. Let’s use it in action.
1 2 |
var numbers = [1, 2, 3, 5, 6, 20]; numbers.random() // will generate 2 in my case |
That’s it. Happy coding.
Update:
Actually it’s very bad practice to extend prototype. Reasons are problems with browser compatibility. It’s better to create a function and use it. Or you can always use libs like underscore and it’s method sample.
Today while working on MEDinar we had a problem with our presentation videos. We are using YouTube to host it. But when our customer finishes watching, they are suggested related videos. All of them were not related plus it’s not good for them to get distracted. The video is about how MEDinar is awesome and not some cat videos.
We are using YT.Player to embed video.
1 2 3 4 5 6 7 |
player = new YT.Player('player', { height: '960', width: '1280', videoId: 'a4iXXPoy59U', events: { 'onStateChange': onPlayerStateChange } }); |
The reason is why we are using this way is to also track events when usre pauses and completes watching videos.
Many solutions on the internet suggested to specify additional parameter rel=0 to end of the URL. But how to do this with YT.Player? We needed to check official Google documentation https://developers.google.com/youtube/player_parameters#rel. By adding additional parameter in YT.Player initialization, we were able to remove related videos. The final code is.
1 2 3 4 5 6 7 8 |
player = new YT.Player('player', { height: '960', width: '1280', videoId: 'a4iXXPoy59U', playerVars: { rel: '0'}, events: { 'onStateChange': onPlayerStateChange } }); |
Simple, right?