I have block of AngularJS code that looks like the following:
<ion-list can-swipe="true">
<ion-item ng-repeat="item in items" simplify-item>
<div class="row" style="background-color:orange;">
Hello
</div>
</ion-item>
</ion-list>
The directives come from the ionic framework. Still, I am trying to add my own directive to this. I want to set a CSS property via an attribute called "simplify-item". My directive to make this happen looks like this:
myApp.directive('simplifyItem', function() {
return {
restrict:'A',
link: function(element) {
console.log('linking element');
if (element === null) {
console.log('element is null');
} else {
var result = element.hasClass('item-complex');
if (result === null) {
console.log('result is null.');
} else {
console.log('Sweet!');
}
}
}
};
});
When the line var result = element.hasClass('item-complex'); gets executed, I get an exception. The exception says:
undefined is not a function
I don't understand why I can't do this. What am I doing wrong?
try like this
myApp.directive('simplifyItem', function() {
return {
restrict:'A',
link: function(scope , iElement, iAttrs) {
var element = iElement;
console.log('linking element');
if (element === null) {
console.log('element is null');
} else {
var result = element.hasClass('item-complex');
if (result === null) {
console.log('result is null.');
} else {
console.log('Sweet!');
}
}
}
};
});
explanation from angular docs
Directives that want to modify the DOM typically use the link option. link takes a function with the following signature, function link(scope, element, attrs) { ... } where:
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is a hash object with key-value pairs of normalized attribute names and their corresponding
attribute values.
Related
I am very new to angularjs and I just cant figure out how I would go about validating my inputs.
I have an input which should never be empty. The model will always have a value. Whenever a user inputs invalid data (i.e nothing or maybe invalid characters) I would like the input to revert to its original value.
For instance, If the initial value was 50 and I deleted all of that (or entered text) and deselected the input, I would expect the input's value to change back to 50.
Here is my controller:
var MyController = null;
app.controller("MyController", ["$scope", function($scope) {
$scope.data = myData;
$scope.validate = function(value, oldValue) {
if(!value || value == "") {
// Do something after validation
value = oldValue;
}
// Our value should be valid now (either replaced or changed)
}
MyController = $scope;
}]);
And my HTML (i would rather not have to type data.something twice if possible):
<input type="text" ng-model="data.something" ng-change="validate()" />
Small Clarification: If the value of the input is "50" and a user removes the "0", the input would be at "5" which is valid. However if the user adds a "x" after I DONT want the input to change, it should still be at "5". However if all the input is empty, I would like onBlur for the original value to be placed back into the input and the model unchanged.
Yep, you need ng-blur and probably ng-focus:
<input type="text" ng-model="data.something" ng-focus="store()" ng-blur="revertIfInvalid()"/>
$scope.store = function() {
$scope.stored = $scope.data.something;
}
$scope.revertIfInvalid= function() {
if (!$scope.data.something) {
$scope.data.something = $scope.stored;
}
}
This will work, but to make it reusable, you then want to make directive for this, like:
app.directive('fancydirective', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(sc, elem, attrs, ngModelCtrl) {
var stored;
elem.on('focus', function() {
sc.$apply(function() {
stored = ngModelCtrl.$modelValue;
});
});
elem.on('blur', function() {
sc.$apply(function() {
if (ngModelCtrl.$invalid) {
ngModelCtrl.$setViewValue(stored);
ngModelCtrl.$render();
}
});
});
}
}
});
http://plnkr.co/edit/fiRKS765Kyh6ikRqc8if?p=preview
I'm trying to buld a custom directive that is actually a wrapper around input field (to simplify formatting, encapsulate animations, etc.).
One goal is to use ngModel so my directive would also be compatible with ng-maxlength, ng-required and similar directives depending on ng-Model.
I've created this plunkr with my current state:
http://embed.plnkr.co/xV8IRqTmQmKEBhRhCfBQ/
My problem is that ng-required seems to be working, but invalidates only the complete form (so that form.$invalid becomes true), but not the element itself form.element.$invalid remains false.
Also, ng-maxlength / ng-minlength does not seem to have any effect at all.
What am I missing here? Any hints welcome :)
Hi everyone and thanks a lot for your answers!
I finally figured out what the missing piece for me was: the name attribute which is used by the form to reference the element MUST NOT be on the inner input field.
It has to reside on the outer element that carries the mg-model that also gets the other directives (that interact with the ng-model).
So, to illustrate this in more detail, before my template looked like:
<span class="custom-input-element">
<label for="{{elementId}}-input">{{elementLabel}}<span class="required-marker" ng-if="elementRequired">*</span></label>
<input id="{{elementId}}-input" type="text" name="{{elementName}}" ng-trim ng-model="value" ng-init="focused = false" ng-focus="focused = true" ng-blur="focused = false"/>
</span>
Which was used like
<custom-input id="foldername" name="foldername" label="Folder Name:"
ng-model="folder.name" ng-maxlength="15" ng-required="true"> </custom-input>
Notice the name={{elementName}} that basically overlayed the name="foldername" on my directive's tag.
After removing it from the directives template, the form references my directive and the ngModel on my directive for validation - the input and the inner ng-model keeps hidden. Thus, the interaction with other directives like ng-maxlength and mg-minlength and also custom directives/validators works as expected.
So now, not only the form gets invalidated but also each element is validated in the expected way.
I updated my plunker where everything is working as desired now: http://embed.plnkr.co/i3SzV8H7tnkUk2K9Pq6m/
Thanks for your time and your very valuable input!
I have created one that works, i'll try to show you the relevant part of the code.
The one really annoying point was to reattach the input and the validation to the form of the parent controller.
For this i had to cc a bunch of private code from angular :
/**
* start cc from angular.js to modify $setValidity of ngModel to retrieve the parent form...
*/
var VALID_CLASS = 'data-ng-valid',
INVALID_CLASS = 'data-ng-invalid',
PRISTINE_CLASS = 'data-ng-pristine',
DIRTY_CLASS = 'data-ng-dirty',
UNTOUCHED_CLASS = 'data-ng-untouched',
TOUCHED_CLASS = 'data-ng-touched',
PENDING_CLASS = 'data-ng-pending';
function addSetValidityMethod(context) {
var ctrl = context.ctrl,
$element = context.$element,
classCache = {},
set = context.set,
unset = context.unset,
parentForm = context.parentForm,
$animate = context.$animate;
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
ctrl.$setValidity = setValidity;
function setValidity(validationErrorKey, state, controller) {
if (state === undefined) {
createAndSet('$pending', validationErrorKey, controller);
} else {
unsetAndCleanup('$pending', validationErrorKey, controller);
}
if (!isBoolean(state)) {
unset(ctrl.$error, validationErrorKey, controller);
unset(ctrl.$$success, validationErrorKey, controller);
} else {
if (state) {
unset(ctrl.$error, validationErrorKey, controller);
set(ctrl.$$success, validationErrorKey, controller);
} else {
set(ctrl.$error, validationErrorKey, controller);
unset(ctrl.$$success, validationErrorKey, controller);
}
}
if (ctrl.$pending) {
cachedToggleClass(PENDING_CLASS, true);
ctrl.$valid = ctrl.$invalid = undefined;
toggleValidationCss('', null);
} else {
cachedToggleClass(PENDING_CLASS, false);
ctrl.$valid = isObjectEmpty(ctrl.$error);
ctrl.$invalid = !ctrl.$valid;
toggleValidationCss('', ctrl.$valid);
}
// re-read the state as the set/unset methods could have
// combined state in ctrl.$error[validationError] (used for forms),
// where setting/unsetting only increments/decrements the value,
// and does not replace it.
var combinedState;
if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
combinedState = undefined;
} else if (ctrl.$error[validationErrorKey]) {
combinedState = false;
} else if (ctrl.$$success[validationErrorKey]) {
combinedState = true;
} else {
combinedState = null;
}
toggleValidationCss(validationErrorKey, combinedState);
parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
}
function createAndSet(name, value, controller) {
if (!ctrl[name]) {
ctrl[name] = {};
}
set(ctrl[name], value, controller);
}
function unsetAndCleanup(name, value, controller) {
if (ctrl[name]) {
unset(ctrl[name], value, controller);
}
if (isObjectEmpty(ctrl[name])) {
ctrl[name] = undefined;
}
}
function cachedToggleClass(className, switchValue) {
if (switchValue && !classCache[className]) {
$animate.addClass($element, className);
classCache[className] = true;
} else if (!switchValue && classCache[className]) {
$animate.removeClass($element, className);
classCache[className] = false;
}
}
function toggleValidationCss(validationErrorKey, isValid) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
}
}
function arrayRemove(array, value) {
var index = array.indexOf(value);
if (index >= 0) {
array.splice(index, 1);
}
return index;
}
function isBoolean(value) {
return typeof value === 'boolean';
};
var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator) {
separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
return (pos ? separator : '') + letter.toLowerCase();
});
}
function isObjectEmpty(obj) {
if (obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
return false;
}
}
}
return true;
};
/**
* end of cc
*/
Then in the link function :
function(scope, element, attrs, ctrl, transclude){
[...]
scope.form = element.parent().controller('form');
var transcludedContent = transclude(scope.$parent);
// find the input
var fieldContent = findFormField(transcludedContent);
var ngModelCtrl = angular.element(fieldContent).controller('ngModel');
if(!ngModelCtrl){
throw 'transcluded form field must have a ng-model';
}
addSetValidityMethod({
ctrl: ngModelCtrl,
$element: angular.element(fieldContent),
set: function(object, property, controller) {
var list = object[property];
if (!list) {
object[property] = [controller];
} else {
var index = list.indexOf(controller);
if (index === -1) {
list.push(controller);
}
}
},
unset: function(object, property, controller) {
var list = object[property];
if (!list) {
return;
}
arrayRemove(list, controller);
if (list.length === 0) {
delete object[property];
}
},
parentForm: scope.form,
$animate: $animate
});
scope.form.$addControl(ngModelCtrl);
element.html(template);
$compile(element.contents())(scope);
element.find('.ng-form-field-content').append(transcludedContent);
// remove the control from the form, otherwise an ng-if that hide an invalid input will block your form
scope.$on(
"$destroy",
function handleDestroyEvent() {
scope.form.$removeControl(ngModelCtrl);
});
The template is a variable containing the html of my wrapping around the input. (it generates the label, put a start if required, show a check or cross sign if field valid/invalid,...).
EDIT :
With my directive i can do :
<div my-directive>
<input/textarea/select ng-model="", required/ng-required, ng-pattern, <custom directive validation>...
</div>
And it will give something like
<div my-directive>
<label for=<input'sname>>Texte</label>
<input [the input will all his attrs]/>
[some extra content]
</div>
I can even put some intermediary nodes or have multiple input that point to the same ng-model (like with checkbox/Radio buttons), however it won't works with different ng-models. I didn't push it that far.
I am using the typeahead directive in AngularJS and it works fine. However, I would like to have a button outside of the input that when clicked would show the typeahead dropdown. Here is a snippet of what I am after...
<li class="input">
<input focus-me="click" ng-model="something"
typeahead="state for state in Suggestions | filter:$viewValue:stateComparator" typeahead-focus typeahead-focus-first="false" typeahead-on-select="updateTagInput(newTagName)">
Open
</li>
Ok, I am having an absolutely terrible time trying to create a JSFiddle or even a Plunkr for this, so I will just give you the code for this directive.
This directive originally comes from..
This epic Bootstrap library!
..and I stole it and played with it. If you would like to use it, you will need the "Bootstrap" (its really a subset of angular directives) library that I linked to. You can make your own subset of this library, but I am not entirely sure of all of the dependencies my directive has as I am using the entire library in my project. Basically, you need any directive that starts with "typeahead".
As you can see, I have named the directive wwTypeahead (that "ww" is for WebWanderer!). It is a very easy to use directive and it works just like the original.
<input
class="form-control"
type="text"
spellcheck="false"
ng-model="selection"
ng-trim="false"
placeholder="Search Here"
ww-typeahead="key as key.label for key in list"
typeahead-on-select="selectionMade($item, $model, $label)"
typeahead-min-length="0"
/>
The really important part to note is the attribute typeahead-min-length="0" which has really been the heart of many discussions online. I managed to make that work.
This directive is meant to take the place of the typeahead directive in the library I linked to. Your typeahead list will be shown on focus of your input box. No, the list does not show on the click of a button, but hopefully getting there will be baby-steps from here. If you need help implementing that, I will be happy to help.
/*
NOTE:
The following directive is a modification of the
Angular typeahead directive. The normal directives,
unfortunately, do not allow matching on 0 length values
and the user may want a returned list of all values during
the lack of input.
This directives was taken from ...
http://angular-ui.github.io/bootstrap/
..and modified.
*/
angular.module('ui.directives', []).directive('wwTypeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
function($compile, $parse, $q, $timeout, $document, $position, typeaheadParser)
{
var HOT_KEYS = [9, 13, 27, 38, 40];
return {
require:'ngModel',
link:function(originalScope, element, attrs, modelCtrl)
{
//SUPPORTED ATTRIBUTES (OPTIONS)
//minimal no of characters that needs to be entered before typeahead kicks-in
//var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
var testEval = originalScope.$eval(attrs.typeaheadMinLength);
var minSearch = !isNaN(parseFloat(testEval)) && isFinite(testEval) || 1;
//minimal wait time after last character typed before typehead kicks-in
var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
//should it restrict model values to the ones selected from the popup only?
var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
//binding to a variable that indicates if matches are being retrieved asynchronously
var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
//a callback executed when a match is selected
var onSelectCallback = $parse(attrs.typeaheadOnSelect);
var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
//INTERNAL VARIABLES
//model setter executed upon match selection
var $setModelValue = $parse(attrs.ngModel).assign;
//expressions used by typeahead
var parserResult = typeaheadParser.parse(attrs.cmcTypeahead);
//pop-up element used to display matches
var popUpEl = angular.element('<typeahead-popup></typeahead-popup>');
popUpEl.attr({
matches: 'matches',
active: 'activeIdx',
select: 'select(activeIdx)',
query: 'query',
position: 'position'
});
//custom item template
if(angular.isDefined(attrs.typeaheadTemplateUrl))
{
popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
}
//create a child scope for the typeahead directive so we are not polluting original scope
//with typeahead-specific data (matches, query etc.)
var scope = originalScope.$new();
originalScope.$on('$destroy', function()
{
scope.$destroy();
});
var resetMatches = function()
{
scope.matches = [];
scope.activeIdx = -1;
};
var getMatchesAsync = function(inputValue)
{
var matchParsePrefix = originalScope.$eval(attrs.typeaheadParsePrefix);
var locals = {
$viewValue: inputValue.indexOf(matchParsePrefix) === 0 ? inputValue.substring(matchParsePrefix.length, (inputValue.length + 1)) : inputValue
};
isLoadingSetter(originalScope, true);
$q.when(parserResult.source(scope, locals)).then(function(matches)
{
//it might happen that several async queries were in progress if a user were typing fast
//but we are interested only in responses that correspond to the current view value
//if(matches && inputValue === modelCtrl.$viewValue)
/*
Ehh.. that didn't seem to work when I "cleared" the input box
*/
if(matches)
{
if(matches.length > 0)
{
scope.activeIdx = 0;
scope.matches.length = 0;
//transform labels
for(var i = 0; i < matches.length; i++)
{
locals[parserResult.itemName] = matches[i];
scope.matches.push({
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = inputValue;
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
//due to other elements being rendered
scope.position = $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
}
else if(minSearch === 0)
{
resetMatches();//temp
}
else
{
resetMatches();
}
isLoadingSetter(originalScope, false);
}
}, function()
{
resetMatches();
isLoadingSetter(originalScope, false);
});
};
resetMatches();
/*
Can't figure out how to make this work...*/
if(attrs.hasOwnProperty('typeaheadBindMatchReloader'))
{
$parse(attrs.typeaheadBindMatchReloader).assign(scope, function()
{
getMatchesAsync(element[0].value);
});
}
//we need to propagate user's query so we can higlight matches
scope.query = undefined;
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
var timeoutPromise;
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
modelCtrl.$parsers.unshift(function(inputValue)
{
resetMatches();
if((inputValue && inputValue.length >= minSearch)
|| minSearch === 0)
{
if(waitTime > 0)
{
if(timeoutPromise)
{
$timeout.cancel(timeoutPromise);//cancel previous timeout
}
timeoutPromise = $timeout(function()
{
getMatchesAsync(inputValue);
}, waitTime);
}
else
{
getMatchesAsync(inputValue);
}
}
if(isEditable)
{
return inputValue;
}
else
{
modelCtrl.$setValidity('editable', false);
return undefined;
}
});
modelCtrl.$formatters.push(function(modelValue)
{
var candidateViewValue, emptyViewValue;
var locals = {};
if(inputFormatter)
{
locals['$model'] = modelValue;
return inputFormatter(originalScope, locals);
}
else
{
//it might happen that we don't have enough info to properly render input value
//we need to check for this situation and simply return model value if we can't apply custom formatting
locals[parserResult.itemName] = modelValue;
candidateViewValue = parserResult.viewMapper(originalScope, locals);
locals[parserResult.itemName] = undefined;
emptyViewValue = parserResult.viewMapper(originalScope, locals);
return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
}
});
scope.select = function(activeIdx)
{
//called from within the $digest() cycle
var locals = {};
var model, item;
locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
model = parserResult.modelMapper(originalScope, locals);
$setModelValue(originalScope, model);
modelCtrl.$setValidity('editable', true);
onSelectCallback(originalScope, {
$item: item,
$model: model,
$label: parserResult.viewMapper(originalScope, locals)
});
resetMatches();
//return focus to the input element if a mach was selected via a mouse click event
element[0].focus();
};
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
element.bind('keydown', function(evt)
{
//typeahead is open and an "interesting" key was pressed
if(scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1)
return;
evt.preventDefault();
if(evt.which === 40)
{
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();
}
else if(evt.which === 38)
{
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();
}
else if(evt.which === 13 || evt.which === 9)
{
scope.$apply(function()
{
scope.select(scope.activeIdx);
});
}
else if(evt.which === 27)
{
evt.stopPropagation();
resetMatches();
scope.$digest();
}
});
// Keep reference to click handler to unbind it.
var dismissClickHandler = function(evt)
{
if(element[0] !== evt.target)
{
resetMatches();
scope.$digest();
}
else
{
getMatchesAsync(element[0].value);
}
};
$document.bind('click', dismissClickHandler);
originalScope.$on('$destroy', function()
{
$document.unbind('click', dismissClickHandler);
});
element.after($compile(popUpEl)(scope));
}
};
}]);
Call To Action:
Somebody PLEASE make a working example of this typeahead directive! I would forever be in debt to you! (well, not really but it would make me very happy)
DISCLAIMER:
I understand that this answer is in no way orthodox. I did not provide the askee (askee?) with a direct answer to the question, yet I did provide the tools that I believe are needed to get to his/her answer. I understand that I should spend the time to make a working example, but I am a very busy man and simply wished to share my work with the community, as I have seen this question asked too many times while I sit back and hold the answer. Please let me know if you have any issues, questions, or complications. I am happy to help.
Thanks!
<input
class="form-control"
spellcheck="false"
focus-me="click" ng-model="something"
ng-trim="false"
placeholder="Search Here"
uib-typeahead="key as key.label for key in list | filter:{label:$viewValue}"
typeahead-on-select="openTypeAhead($item, $model, $label)"
typeahead-min-length="0"
/>
in controller angularjs
$scope.openTypeAhead = ($item, $model, $label) =>{ console.log('arg =>',$item, $model, $label);}
I am converting some code to use angular directives. I am new at this..
The old code is:
$('.header .mobile-nav ').append($('.navigation').html());
$('.header .mobile-nav li').bind('click', function(e) {
var $this = $(this);
var $ulKid = $this.find('>ul');
var $ulKidA = $this.find('>a');
if ($ulKid.length === 0 && $ulKidA[0].nodeName.toLowerCase() === 'a') {
window.location.href = $ulKidA.attr('href');
}
else {
$ulKid.toggle(0, function() {
if ($(this).css('display') === 'block') {
$ulKidA.find('.icon-chevron-down').removeClass('icon-chevron-down').addClass('icon-chevron-up');
}
else {
$ulKidA.find('.icon-chevron-up').removeClass('icon-chevron-up').addClass('icon-chevron-down');
}
});
}
e.stopPropagation();
return false;
});
My attempt at creative the correct directives is as follows:
app.directive('mobilenav', function () {
return { template: $('.navigation').html() };
});
app.directive('mobileNavMenu', function () {
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs) {
var iElement = element.find('.header .mobile-nav li');
iElement.bind('click', function (e) {
var $this = $(this);
var $ulKid = $this.find('* > ul');
var $ulKidA = $this.find('* > a');
if ($ulKid.length === 0 && $ulKidA[0].nodeName.toLowerCase() === 'a') {
window.location.href = $ulKidA.attr('href');
} else {
$ulKid.toggle(0, function () {
if ($(this).css('display') === 'block') {
$ulKidA.find('.icon-chevron-down').removeClass('icon-chevron-down').addClass('icon-chevron-up');
} else {
$ulKidA.find('.icon-chevron-up').removeClass('icon-chevron-up').addClass('icon-chevron-down');
}
});
}
e.stopPropagation();
return false;
});
}
});
The "click" event is not being bound. I think it could be because it is not finding the element. Am I using the right approach? Can you help me fix my directives?
Thanks!
I think there are a few things you could be doing better with this approach. First, I don't think your directives should searching for or modifying other elements by CSS.
So first I would modify this directive to bind directly on the passed in element:
function link(scope, element, attrs) {
//var iElement = element.find('.header .mobile-nav li');
iElement.bind('click', function (e) {
...
This might mean you need to apply your directive to a different element than you were originally intending.
Next, for the section where you toggle the chevrons, I think you might want to do this differently. You could for example, set a variable in the scope, or in a service, and then create another directive that watches that variable and responds to the changes in it. Alternatively, you might be able to use ng-show/ng-hide to elements based on the change.
These questions might be helpful to you as well:
Sharing data between directives
Changing CSS from AngularJS
I'd like to watch my hide and show expressions on all elements in my app.
I know I can do it by wrapping the show directive with a function which just returns the argument:
<div ng-show="catchShow(myShowExpr == 42)"></div>
However, I'd like to watch all hide/shows across all inputs in my app and the above isn't good enough.
I could also overload the ngShow / ngHide directives though I'd need to reevaluate the expression.
I could also just modify the source since it's quite simple:
var ngShowDirective = ['$animator', function($animator) {
return function(scope, element, attr) {
var animate = $animator(scope, attr);
scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
var fn = toBoolean(value) ? 'show' : 'hide';
animate[fn](element);
//I could add this:
element.trigger(fn);
});
};
}];
Though then I couldn't use the Google CDN...
Is there a nicer way anyone can think of to do this ?
Here's what I've come up with (CoffeeScript)
getDirective = (isHide) ->
['$animator', ($animator) ->
(scope, element, attr) ->
animate = $animator(scope, attr)
last = null
scope.$watch attr.oaShow, (value) ->
value = not value if isHide
action = if value then "show" else "hide"
if action isnt last
scope.$emit 'elementVisibility', { element, action }
animate[action] element
last = action
]
App.directive 'oaShow', getDirective(false)
App.directive 'oaHide', getDirective(true)
Converted to JavaScript:
var getDirective = function(isHide) {
return ['$animator', function($animator) {
//linker function
return function(scope, element, attr) {
var animate, last;
animate = $animator(scope, attr);
last = null;
return scope.$watch(attr.oaShow, function(value) {
var action;
if (isHide)
value = !value;
action = value ? "show" : "hide";
if (action !== last) {
scope.$emit('elementVisibility', {
element: element,
action: action
});
animate[action](element);
}
return last = action;
});
};
}];
};
App.directive('oaShow', getDirective(false));
App.directive('oaHide', getDirective(true));
Another way to approach is to $watch the attribute of ngShow - although this would need to be done within a directive (useful if you're show/hiding an already custom directive)
scope.$watch attrs.ngShow, (shown) ->
if shown
# Do something if we're displaying
else
# Do something else if we're hiding