How to create a <<hold to confirm >> button - angularjs

How would someone go about making a hold to confirm button similar to what designmodo uses?
I have a working version using jQuery but am at a loss how to incorporate this into Angular. Is this something possible with ngAnimate?
jsfiddle css:
path {
stroke-dasharray: 119;
stroke-dashoffset: 119;
}
.draw {
-webkit-animation: dash 3s ease forwards;
}
#-webkit-keyframes dash {
to {
stroke-dashoffset: 0;
}
}
jsfiddle js:
$('.delete-icon').mousedown(function() {
$('path').attr('class', 'draw');
});
$('.delete-icon').mouseup(function() {
$('path').attr('class', 'progress');
});
$("path").bind("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){
console.log('callback');
$('.delete-icon').hide();
});

So figured out how to do this thought I'd leave an answer in case anyone else comes across this.
Big thing was figuring out how to use jQuery to make the callback when the animation completes. There might be a way to do this with Angular but the only callbacks I could find are when the class was added/removed which is not what I needed.
http://plnkr.co/edit/Lafds0KA04mcrolR9mHg?p=preview
var app = angular.module("app", ["ngAnimate"]);
app.controller("AppCtrl", function() {
this.isHidden = false;
this.deleteIt = function() {
this.isHidden = !this.isHidden;
}
app.hidden = false;
});
app.directive("hideMe", function($animate) {
return function(scope, element, attrs) {
scope.$watch(attrs.hideMe, function(newVal) {
if(newVal) {
$animate.addClass(element, "draw");
} else {
$animate.removeClass(element, "draw");
}
});
}
});
app.animation(".draw", function() {
return {
addClass: function(element, className, done) {
//
jQuery(element).animate({
"stroke-dashoffset": 0
}, 3000, "easeOutCubic", function() {
console.log(app.hidden);
});
return function(cancel) {
if(cancel) {
jQuery(element).stop();
}
}
},
removeClass: function(element, className, done) {
//
jQuery(element).animate({
"stroke-dashoffset": 119
}, 350, "linear", function() {
console.log('canceled');
});
return function(cancel) {
jQuery(element).stop();
}
}
}
});

Ok I have a refined answer for this. View here
I dropped using animation and jQuery for two reasons:
I could not figure out how to get jQuery done to callback to
scope.
The jQuery done callback only executed on mouseup after
the animation had completed.
There are probably ways to bypass these but I couldn't figure it out.
Instead I used the angular specific animation classes that will trigger a promise on animation completion. Specifically:
.line.draw {
-webkit-animation: dash 3s ease forwards;
}
.line.draw-add {
}
.line.draw-add-active {
}
#-webkit-keyframes dash {
to {
stroke-dashoffset: 0;
}
}
I didn't need to use .line but kept it in there because lazy.
I also used isolating scope to reference the scope in the controller:
scope: {
'myAnimate': '=',
'deleteTodo': '&'
},
I think that is all the tricky parts to this solution. If anyone has any questions feel free to ask.

Related

How can I write an angular directive to listen to changes in `ngOptions`?

I am using the angular-bootstrap-select plugin to style selects. It is basically a port of the bootstrap-select plugin that hooks into angular.
As of right now it seems to update whenever the ng-model changes. However I would also like to listen to the ng-options
Here is the relevant part of the plugin:
angular.module('angular-bootstrap-select', [])
.directive('selectpicker', ['$parse', '$timeout', selectpickerDirective]);
function selectpickerDirective($parse, $timeout) {
return {
restrict: 'A',
priority: 1000,
link: function (scope, element, attrs) {
function refresh(newVal) {
scope.$applyAsync(function () {
if (attrs.ngOptions && /track by/.test(attrs.ngOptions)) element.val(newVal);
element.selectpicker('refresh');
});
}
attrs.$observe('spTheme', function (val) {
$timeout(function () {
element.data('selectpicker').$button.removeClass(function (i, c) {
return (c.match(/(^|\s)?btn-\S+/g) || []).join(' ');
});
element.selectpicker('setStyle', val);
});
});
$timeout(function () {
element.selectpicker($parse(attrs.selectpicker)());
element.selectpicker('refresh');
});
if (attrs.ngModel) {
scope.$watch(attrs.ngModel, refresh, true);
}
if (attrs.ngDisabled) {
scope.$watch(attrs.ngDisabled, refresh, true);
}
scope.$on('$destroy', function () {
$timeout(function () {
element.selectpicker('destroy');
});
});
}
};
}
An example of the usage:
<select selectpicker ng-options="myOpt.key as myOpt.name for myOpt in myOpts" ng-model="myOptSelection"></select>
So I would like to edit the plugin to listen to changes in myOpts and run element.refresh()
So far the best way I found was to insert the following into the plugin:
if (attrs.ngOptions) {
var watch = attrs.ngOptions.match(/in ([A-z0-9]+)$/);
if (watch) {
scope.$watch(watch[1], refresh, true);
}
}
Seems a little dirty though...I'm pretty new to angular, so I'm not sure what other possibilities there are for ngOptions or if there is a cleaner way to parse it.

replace jQuery animation with built in angular animation

Given this template:
<div fade>
<h2>
TEST {{ headline.Title }}
</h2>
</div>
And the following directive:
How do I change this directive to replace the jquery fade with built in angular animations?
I require the text to fade out, get replaced, and then fade in.
newman.directive('fade', ['$interval', function($interval) {
return function ($scope, element, attrs) {
$scope.index = 0;
$scope.news = $interval(function () {
// REPLACE JQUERY BELOW
$(element).fadeOut('fast', function() {
$scope.index = $scope.getValidNewHeadlineIndex();
// view is currently correctly updated by the line below.
$scope.headline = $scope.headlines[$scope.index];
$(element).fadeIn('slow'); // REPLACE JQUERY HERE TOO!
});
}, 10000);
}
}]);
Figured it out, mostly...
This is for anyone else battling with angular-js animation. A working CODEPEN.
The basic procedure is to create some CSS to create the animation, and add a call to $animate.enter(... to the 'fade' directive.
$animate.leave doesn't seem to be required. I will add more detail when I know more.
the modified directive:
app.directive('fade', ['$animate', '$interval', function($animate, $interval) {
return function ($scope, element, attrs) {
$interval(function () {
$animate.enter(element, element.parent());
$scope.headline = $scope.next();
/* $animate.leave(element); */ // not required?
}, 6000);
}
}]);
the style sheet entries:
.fade {
transition: 2s linear all;
-webkit-transition: 2s linear all;
}
.fade.ng-enter {
opacity: 0;
}
.fade.ng-enter.ng-enter-active {
opacity: 1;
}
alternate solution, using TweenMax
This solution is suitable for (you guessed it - internet explorer < 10)
TweenMax solution using onComplete.
app.directive('fade', ['$animate', '$interval', function($animate, $interval) {
var fadeOut = function(target, done){
TweenMax.to(
target, 0.2,/*{'opacity':'1',ease:Ease.linear},*/
{'opacity':'0',ease:Ease.linear, onComplete:done });
};
var fadeInUp = function(target){
var tl = new TimelineMax();
tl.to(target,0,{'opacity':'0',top:'+=50'})
.to(target,1,{'opacity':'1',top:'-=50',ease:Quad.easeOut});
};
return function ($scope, element, attrs) {
$interval(function () {
fadeOut(element, function() {
$scope.$apply(function() {
$scope.headline = $scope.next();
fadeInUp(element);
});
});
}, 4000);
}
}]);

Angular Javascript animation works with ng-if but not ng-show

I've been experimenting with angular animations and have come to the conclusion that Angular JavaScript based animations are not triggered using ng-if. I developed a simple Plunkr demonstrating the inconsistency. Essentially, the problem with this is I don't want to be appending and removing elements from the DOM (ng-if) and would rather use ng-show because one of the items being animated is an HTML5 video that I would like to begin pre-loading upon page load. The code from the Plunkr is as follows:
HTML
<button ng-click="on4=!on4">JS If Transition</button>
<div class="js-if-element" ng-if="on4">I'm animated by JS and ng-if</div>
<button ng-click="on5=!on5">JS Show Transition</button>
<div class="js-show-element" ng-show="on5">I'm animated by JS and ng-show</div>
JS
app.animation('.js-if-element', function() {
return {
enter : function(element, done) {
element.css('opacity',0);
jQuery(element).animate({
opacity: 1
}, done);
return function(isCancelled) {
if(isCancelled) {
jQuery(element).stop();
}
}
},
leave : function(element, done) {
element.css('opacity', 1);
jQuery(element).animate({
opacity: 0
}, done);
return function(isCancelled) {
if(isCancelled) {
jQuery(element).stop();
}
}
}
}
});
app.animation('.js-show-element', function() {
return {
enter : function(element, done) {
element.css('opacity',0);
jQuery(element).animate({
opacity: 1
}, done);
return function(isCancelled) {
if(isCancelled) {
jQuery(element).stop();
}
}
},
leave : function(element, done) {
element.css('opacity', 1);
jQuery(element).animate({
opacity: 0
}, done);
return function(isCancelled) {
if(isCancelled) {
jQuery(element).stop();
}
}
}
}
});
Now if you execute the code in this Plunkr the element with the ng-if directive will animate it's opacity while the element with the ng-show will not trigger the animation. Also, in the Plunkr I've tested both scenarios using keyframes/CSS transitions and both ng-show and ng-if work http://plnkr.co/edit/yzXYJnMrvYkWBnMtMLAm?p=preview
I know this is old, but incase others find it show and hide don't use enter leave. You'll have to use beforeAddClass and beforeRemoveClass.

AngularJS Animations with JavaScript

See this JSFiddle: http://jsfiddle.net/5mUsH/3/
I'm trying to do a really simple JavaScript animation in AngularJS and jQuery. (I'm not using CSS animations because I want to support older browsers and also do more complex animations.) The code in the fiddle is from the AngularJS user guide (but slightly simplified): http://docs.angularjs.org/guide/animations
But—it doesn't work! The DOM is updated immediately (without animating). Any ideas? Thanks!
Here is the relevant markup from the JSFiddle:
<div class="sample" ng-show="checked">
Visible...
</div>
And the JavaScript:
angular.module('App', ['ngAnimate']).animation('.sample', function() {
return {
enter : function(element, done) {
element.css('opacity',0);
jQuery(element).animate({
opacity: 1
}, done);
// optional onDone or onCancel callback
// function to handle any post-animation
// cleanup operations
return function(isCancelled) {
if(isCancelled) {
jQuery(element).stop();
}
}
},
leave : function(element, done) {
element.css('opacity', 1);
jQuery(element).animate({
opacity: 0
}, done);
// optional onDone or onCancel callback
// function to handle any post-animation
// cleanup operations
return function(isCancelled) {
if(isCancelled) {
jQuery(element).stop();
}
}
},
}
});
I approached it in a slightly different way as the ng-show was overriding the animations. Since you wanted to use jQuery:
http://jsfiddle.net/wiredprairie/5tFCZ/1/
angular.module('App', ['ngAnimate']).animation('.sample', function () {
return {
addClass: function (element, className, done) {
if (className === 'hidden') {
jQuery(element)
.css({
opacity: 1
})
.animate({
opacity: 0
}, 500, done);
} else {
done();
}
},
removeClass: function (element, className, done) {
if (className === 'hidden') {
jQuery(element)
.css({
opacity: 0
})
.animate({
opacity: 1
}, 500, done);
} else {
done();
}
}
}
});
Basically, the hidden CSS class is toggled, then the corresponding animation code executes.

Angular ui-router's ui-sref created dynamically in a directive?

I am working on a line of business application that is using Angular to create a SPA around a Node.js api server. I decided on using ui-router cause of the state-machine and their intuitive way of embedding urls but I chanced upon a slight challenge when creating dynamic URLs within a directive.
I am using jQuery Datatables for my data grid as a directive but any action links generated using 'fnRender' don't seem to compile the 'ui-sref' to their respective 'href'links. The directive code is as follows:
app.directive('datatable', function ($http) {
return {
restrict: 'A',
link: function ($scope, $elem, attrs) {
var responsiveHelper;
var breakpointDefinition = {
tablet: 1024,
phone : 480
};
var options = {
bDeferRender: true,
sPaginationType: "full_numbers",
oLanguage: {
sEmptyTable: "No records returned",
sSearch: "<span>Search:</span> ",
sInfo: "Showing <span>_START_</span> to <span>_END_</span> of <span>_TOTAL_</span> entries",
sLengthMenu: "_MENU_ <span>entries per page</span>",
sProcessing: "Loading..."
},
sDom: "lfrtip",
oColVis: {
buttonText: "Change columns <i class='icon-angle-down'></i>"
},
oTableTools: {
sSwfPath: "js/plugins/datatable/swf/copy_csv_xls_pdf.swf"
},
bAutoWidth : false,
fnPreDrawCallback: function () {
if (!responsiveHelper) {
responsiveHelper = new ResponsiveDatatablesHelper($elem, breakpointDefinition);
}
},
fnRowCallback : function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
responsiveHelper.createExpandIcon(nRow);
},
fnDrawCallback : function (oSettings) {
responsiveHelper.respond();
}
};
if (typeof $scope.dtOptions !== 'undefined') {
angular.extend(options, $scope.dtOptions);
}
if (attrs['dtoptions'] === undefined) {
for (property in attrs) {
switch (property) {
case 'sajaxsource':
options['sAjaxSource'] = attrs[property];
break;
case 'sajaxdataprop':
options['sAjaxDataProp'] = attrs[property];
break;
}
}
} else {
angular.extend(options, $scope[attrs['dtoptions']]);
}
if (typeof options['sAjaxSource'] === 'undefined') {
throw "Ajax Source not defined! Use sajaxsource='/api/v1/*'";
}
if (typeof options['fnServerData'] === 'undefined') {
options['fnServerData'] = function (sSource, aoData, resultCb) {
$http.get(sSource, aoData).then(function (result) {
resultCb(result.data);
});
};
}
options.aoColumnDefs = [];
$elem.find('thead th').each(function() {
var colattr = angular.element(this).data();
if (colattr.mdata) {
if (colattr.mdata.indexOf("()") > 1) {
var fn = $scope[colattr.mdata.substring(0, colattr.mdata.length - 2)];
if (typeof fn === 'function') {
options.aoColumnDefs.push({
mData: fn,
sClass: colattr.sclass,
aTargets: [colattr.atargets]
});
} else {
throw "mData function does not exist in $scope.";
}
} else {
options.aoColumnDefs.push({
mData: colattr.mdata,
sClass: colattr.sclass,
bVisible: colattr.bvisible,
aTargets: [colattr.atargets]
});
}
} else {
if (colattr.fnrender.indexOf("()") > 1) {
var fn = $scope[colattr.fnrender.substring(0, colattr.fnrender.length - 2)];
if (typeof fn === 'function') {
options.aoColumnDefs.push({
fnRender: fn,
sClass: colattr.sclass,
aTargets: [colattr.atargets]
});
} else {
throw "fnRender function does not exist in $scope.";
}
} else {
options.aoColumnDefs.push({
fnRender: function (oObj) {
return "<a tooltip class='btn' title='View' ui-sref=\""+colattr.tag+".show({slug:\'"+oObj.aData._id+"\'})\"><center class=\"icon-search\"></center></a>";
},
sClass: colattr.sclass,
bVisible: colattr.bvisible,
aTargets: [colattr.atargets]
});
}
}
});
$elem.dataTable(options);
$(".dataTables_length select").wrap("<div class='input-mini'></div>").chosen({disable_search_threshold: 9999999 });
}
}
});
It runs with out complications and even generates the following anchor tag:
<a ui-sref="organisation.show({slug:'527a44c02aa9ce3a1c3fbc17'})"></a>
However, ui-router doesn't compile it to a respective state url. What could be the issue? Is there some configuration I may have missed?
Thanks
Are you using any other directives or expressions within your data table? I'm guessing those wouldn't work either, because it looks like Angular never gets the opportunity to compile any of the HTML you're generating.
This has nothing to do with uiSref and everything to do with writing directives correctly. In terms of best practices, this directive has way too much code. You should look at decomposing it into multiple nested directives and straight HTML. I'd suggest spending some time learning about transclusion and doing nested directives with directive controllers.
Leaving aside best practice, I just encountered and resolved this issue myself. Because DataTables is modifying the DOM outside of Angular's event loop, the ui-sref attributes don't get compiled. It's a simple fix: you need to call $compile on each row as it's created.
Here's my (much simpler) directive:
function datatable($compile) {
return {
restrict: "A",
link: (scope, elem, attrs) => {
// THE IMPORTANT BIT
scope.options.createdRow = function (row) {
$compile(row)(scope);
};
var api = elem.DataTable(scope.options);
var handleUpdates = function (newData) {
var data = newData || null;
if (data) {
api.clear();
api.rows.add(data).draw();
}
};
scope.$watch("options.data", handleUpdates, true);
},
scope: { options: "=" }
};
}

Resources