Skip to content

Instantly share code, notes, and snippets.

@krosti
Created April 15, 2015 19:43
Show Gist options
  • Save krosti/eb545870b78cae19a14f to your computer and use it in GitHub Desktop.
Save krosti/eb545870b78cae19a14f to your computer and use it in GitHub Desktop.
Angular-oneway directive
'use strict';
var toBoolean = function(value) {
if (value && value.length !== 0) {
var v = angular.lowercase("" + value);
value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]');
} else {
value = false;
}
return value;
};
var isIE = function() {
var ie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
if (isNaN(ie)) {
ie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
}
return ie;
}
var showHideBinder = function(elm, attr, value) {
var show = (attr === 'show') ? '' : 'none';
var hide = (attr === 'hide') ? '' : 'none';
elm.css('display', toBo olean(value) ? show : hide);
};
var classBinder = function(elm, value) {
if (angular.isObject(value) && !angular.isArray(value)) {
var results = [];
angular.forEach(value, function(value, index) {
if (value) results.push(index);
});
value = results;
}
if (value) {
elm.addClass(angular.isArray(value) ? value.join(' ') : value);
}
};
var transclude = function(transcluder, scope) {
transcluder.transclude(scope, function(clone) {
var parent = transcluder.element.parent();
var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
angular.forEach(clone, function(node) {
parentNode.insertBefore(node, afterNextSibling);
});
});
};
///-------Directive Methods
angular
.module('tcApp')
.directive('oneway', [function() {
return {
restrict: "AM",
controller: ['$scope', '$element', '$attrs', '$interpolate', function($scope, $element, $attrs, $interpolate) {
return {
watcherRemover: undefined,
binders: [],
group: $attrs.oneName,
element: $element,
ran: false,
addBinder: function(binder) {
this.binders.push(binder);
// In case of late binding (when using the directive bo-name/bo-parent)
// it happens only when you use nested oneway, if the bo-children
// are not dom children the linking can follow another order
if (this.ran) {
this.runBinders();
}
},
setupWatcher: function(onewayValue) {
var that = this;
this.watcherRemover = $scope.$watch(onewayValue, function(newValue) {
if (newValue === undefined) return;
that.removeWatcher();
that.checkBindonce(newValue);
}, true);
},
checkBindonce: function(value) {
var that = this,
promise = (value.$promise) ? value.$promise.then : value.then;
// since Angular 1.2 promises are no longer
// undefined until they don't get resolved
if (typeof promise === 'function') {
promise(function() {
that.runBinders();
});
} else {
that.runBinders();
}
},
removeWatcher: function() {
if (this.watcherRemover !== undefined) {
this.watcherRemover();
this.watcherRemover = undefined;
}
},
runBinders: function() {
while (this.binders.length > 0) {
var binder = this.binders.shift();
if (this.group && this.group != binder.group) continue;
var value = binder.scope.$eval( (binder.interpolate) ? $interpolate(binder.value) : binder.value );
switch (binder.attr) {
case 'oneIf':
if (toBoolean(value)) {
transclude(binder, binder.scope.$new());
}
break;
case 'oneSwitch':
var selectedTranscludes, switchCtrl = binder.controller[0];
if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?'])) {
binder.scope.$eval(binder.attrs.change);
angular.forEach(selectedTranscludes, function(selectedTransclude) {
transclude(selectedTransclude, binder.scope.$new());
});
}
break;
case 'oneSwitchWhen':
var ctrl = binder.controller[0];
ctrl.cases['!' + binder.attrs.oneSwitchWhen] = (ctrl.cases['!' + binder.attrs.oneSwitchWhen] || []);
ctrl.cases['!' + binder.attrs.oneSwitchWhen].push({
transclude: binder.transclude,
element: binder.element
});
break;
case 'oneSwitchDefault':
var ctrl = binder.controller[0];
ctrl.cases['?'] = (ctrl.cases['?'] || []);
ctrl.cases['?'].push({
transclude: binder.transclude,
element: binder.element
});
break;
case 'hide':
case 'show':
showHideBinder(binder.element, binder.attr, value);
break;
case 'class':
classBinder(binder.element, value);
break;
case 'text':
binder.element.text(value);
break;
case 'html':
binder.element.html(value);
break;
case 'style':
binder.element.css(value);
break;
case 'disabled':
binder.element.prop('disabled', value);
break;
case 'src':
binder.element.attr(binder.attr, value);
if (isIE()) binder.element.prop('src', value);
break;
case 'attr':
angular.forEach(binder.attrs, function(attrValue, attrKey) {
var newAttr, newValue;
if (attrKey.match(/^oneAttr./) && binder.attrs[attrKey]) {
newAttr = attrKey.replace(/^oneAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
newValue = binder.scope.$eval(binder.attrs[attrKey]);
binder.element.attr(newAttr, newValue);
}
});
break;
case 'href':
case 'alt':
case 'title':
case 'id':
case 'value':
binder.element.attr(binder.attr, value);
break;
}
}
this.ran = true;
}
};
}],
link: function(scope, elm, attrs, onewayController) {
var value = attrs.oneway && scope.$eval(attrs.oneway);
if (value !== undefined) {
onewayController.checkBindonce(value);
} else {
onewayController.setupWatcher(attrs.oneway);
elm.bind("$destroy", onewayController.removeWatcher);
}
}
};
});
angular.forEach(
[{
directiveName: 'oneShow',
attribute: 'show'
}, {
directiveName: 'oneHide',
attribute: 'hide'
}, {
directiveName: 'oneClass',
attribute: 'class'
}, {
directiveName: 'oneText',
attribute: 'text'
}, {
directiveName: 'oneBind',
attribute: 'text'
}, {
directiveName: 'oneHtml',
attribute: 'html'
}, {
directiveName: 'oneSrcI',
attribute: 'src',
interpolate: true
}, {
directiveName: 'oneSrc',
attribute: 'src'
}, {
directiveName: 'oneHrefI',
attribute: 'href',
interpolate: true
}, {
directiveName: 'oneHref',
attribute: 'href'
}, {
directiveName: 'oneAlt',
attribute: 'alt'
}, {
directiveName: 'oneTitle',
attribute: 'title'
}, {
directiveName: 'oneId',
attribute: 'id'
}, {
directiveName: 'oneStyle',
attribute: 'style'
}, {
directiveName: 'oneDisabled',
attribute: 'disabled'
}, {
directiveName: 'oneValue',
attribute: 'value'
}, {
directiveName: 'oneAttr',
attribute: 'attr'
},
{
directiveName: 'oneIf',
transclude: 'element',
terminal: true,
priority: 1000
}, {
directiveName: 'oneSwitch',
require: 'oneSwitch',
controller: function() {
this.cases = {};
}
}, {
directiveName: 'oneSwitchWhen',
transclude: 'element',
priority: 800,
require: '^oneSwitch'
}, {
directiveName: 'oneSwitchDefault',
transclude: 'element',
priority: 800,
require: '^oneSwitch'
}
],
function(oneDirective) {
return onewayModule.directive(oneDirective.directiveName, function() {
var onewayDirective = {
priority: oneDirective.priority || 200,
transclude: oneDirective.transclude || false,
terminal: oneDirective.terminal || false,
require: ['^oneway'].concat(oneDirective.require || []),
controller: oneDirective.controller,
compile: function(tElement, tAttrs, transclude) {
return function(scope, elm, attrs, controllers) {
var onewayController = controllers[0],
name = attrs.oneParent,
element = onewayController.element.parent(),
parentValue;
if (name && onewayController.group !== name) {
onewayController = undefined;
while (element[0].nodeType !== 9 && element.length) {
if ((parentValue = element.data('$onewayController')) && parentValue.group === name) {
onewayController = parentValue;
break;
}
element = element.parent();
}
if (!onewayController)
throw new Error("Missing controller oneway: " + name);
}
onewayController.addBinder({
element : elm,
attr : oneDirective.attribute || oneDirective.directiveName,
attrs : attrs,
value : attrs[oneDirective.directiveName],
interpolate : oneDirective.interpolate,
group : name,
transclude : transclude,
controller : controllers.slice(1),
scope : scope
});
};
}
};
return onewayDirective;
});
})
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment