So I am trying to make a directive that is encapsulated inside a Bootstrap popover. Almost everything is working - I have event listeners to know when buttons get clicked, links, everything is hunkey-dory - except for the fact that even though I am using ng-model to bind to the input in the innermost directive, nothing is getting updated. It's almost as if it's doing a one-way binding instead of a two-way. FieldValue on the model is always null (its initial state), and is never altered (even though it should be equal to whatever was put in the input field). I have looked through a ton of answers and nothing seems to address the issue.
Here is my code:
Page HTML
<table class="table table-bordered table-striped">
<tbody>
<tr ng-repeat="field in model.CustomFields">
<td>{{field.Title}}</td>
<td>
<a popover field-description="field.CustomField.Description" model-value="field.FieldValue" class="trigger">
<text-custom-field text-model="field"></text-custom-field>{{field.FieldValue || 'Empty'}}
</a>
</td>
</tr>
</tbody>
</table>
Popover Directive
(function() {
'use strict';
angular
.module('app.directives')
.directive('popover', popover);
popover.$inject = ['$compile'];
function popover($compile) {
var directive = {
link: link,
restrict: 'A',
scope: {
fieldDescription: '=',
modelValue: '='
}
};
return directive;
function link(scope, element, attrs) {
$(element).popover({
html: true,
placement: 'top',
title: scope.fieldDescription,
content: function () {
return $(this).parent().find('.js-popover-content').html() + '<button ng-click="submit()" type="submit" class="btn btn-primary btn-sm" style="margin-right: 5px;"><i class="glyphicon glyphicon-ok"></i></button><button type="button" class="btn btn-default btn-sm edit-cancel" ng-click="closePop()" style="margin-right: 5px;"><i class="glyphicon glyphicon-remove"></i></button>';
}
});
scope.closePop = function () {
console.log('close');
$('.trigger').popover('hide');
};
scope.submit = function () {
console.log('submit');
scope.$parent.$broadcast('submit');
};
scope.$parent.submitData = function (value) {
scope.modelValue = value;
console.log('submitted data');
scope.closePop();
};
$(element).on('click', function (e) {
$('.trigger').not(this).popover('hide');
$compile($('.popover.in').contents())(scope);
});
}
}
})();
Inner Directive
(function() {
'use strict';
angular
.module('app.directives')
.directive('textCustomField', textCustomField);
function textCustomField() {
var directive = {
templateUrl: 'url/templates/TextCustomField.html',
restrict: 'E',
transclude: true,
link: link,
scope: {
textModel: '='
}
};
return directive;
function link(scope, element, attrs) {
scope.$parent.$on('submit', function (event) {
console.log('heard submit event');
var value = scope.textModel.FieldValue;
console.log(value);
scope.$parent.submitData(value);
});
}
}
})();
Directive HTML
<div class="js-popover-content hide">
<div class="form-group" style="display: inline-flex;">
<input type="text" class="form-control" ng-model="textModel.FieldValue" style="width: {{textModel.CustomField.DisplaySize}}; margin-right: 5px;">
</div>
</div>
Does anyone have any ideas why this isn't working?
Related
I have created a directive which contains a map of key value pairs. In edit mode i used to populate the values of entries using ng-repeat, Also a user can add more key value pairs when he press a add button
Below is the code
angular.module('key_value_component',
[])
.directive('keyValuePairs', function ($compile, $templateCache) {
return {
templateUrl: 'key_value.tpl.html',
restrict: 'E',
replace: true,
scope: {
entries: "=ngModel"
},
controller: function ($scope) {
$scope.create_key_value = function () {
$scope.$broadcast('ready-to-render');
}
}
};
})
.directive("keyValueInit", function ($compile, $templateCache) {
return {
templateUrl: 'key_value_init.tpl.html',
restrict: 'E',
replace: true,
link: function (scope, element, attributes) {
scope.$on('ready-to-render', function (event) {
list_select = $compile($templateCache.get("key_value_input.tpl.html"))(scope);
element.parents('.key_value_element').append(list_select);
})
}
}
}).directive("keyValue", function ($compile, $templateCache) {
return {
templateUrl: 'key_value_input.tpl.html',
restrict: 'E',
replace: true,
}});
templates :
key_value_tpl.html
<div class="container-fluid">
<div class="key_value_header">
<button class="btn btn-success btn-add pull-right" type="button" ng-click="create_key_value()">
<span class="glyphicon glyphicon-plus"></span>
</button>
<div class="col-sm-4"><strong>key</strong></div>
<div class="col-sm-4"><strong>value</strong></div>
</div>
<div class="key_value_body">
<key-value ng-repeat="(key,value) in entries"></key-value>
<div class="key_value_element">
<key-value-init></key-value-init>
</div>
</div>
key_value_input.tpl.html
<div class="form-group key_value">
<div class="col-sm-4">
<input class="form-control key_value_key" type="text" ng-value="key" ng-readonly="key"/>
</div>
<div class="col-sm-4">
<input class="form-control key_value_value" type="text" ng-value="value" ng-readonly="value"/>
</div>
<input-remove-button></input-remove-button>
key_value_init.tpl.html
<div></div>
If someone add a new key value pair I want to append its value in entries. A submit event is happening outside the directive in the form. the directive is part of that form.
I am referencing the value of the variable in a controller in an ng-class template but its not working.
here is the html directive template URl :
<div class="tooltip-anchor">
<div class=" tooltip-content ehub-body" ng-class="{ 'tooltip__content--disabled': tooltipContentValue}" ng-transclude>Tooltip content</div>
</div>
Here is where i am using the directive in the index page
<div style="text-align:center;">
<ehub-tooltip>Hello i am here, and i am her to stay</ehub-tooltip>over here
<ehub-tooltip>Be nice to people on your way up and they will be nice to you on your way down</ehub-tooltip>click me
</div>
And here is the directive:
in this directive i am creating a variable and setting it to false and also trying to use it in an ng-class attribute
(function (window) {
'use strict';
angular
.module('ehub.component.tooltip', [])
.controller('ehubTooltipCtrl', ['$scope', function ($scope) {
$scope.tooltipContentValue = false;
}])
.directive('ehubTooltip', ehubTooltip);
function ehubTooltip() {
var directive = {
controller: "ehubTooltipCtrl",
link: link,
transclude: true,
templateUrl: 'ehub-tooltip.html',
restrict: 'E'
};
return directive;
function link(scope, element, attrs) {
scope.keyupevt = function () {
if (event.keyCode === 27) {
$scope.tooltipContentValue = true;
}
}
}
}
})();
Try this working jsfiddle.
angular.module('ExampleApp', ['ngMessages'])
.controller('ExampleController', function($scope) {
})
.directive('ehubTooltip', function() {
var directive = {
link: link,
transclude: true,
template: '<div class="tooltip-anchor"><div class=" tooltip-content ehub-body" ng-class="{ \'tooltip__content--disabled\': tooltipContentValue}" ng-transclude>Tooltip content</div></div>',
restrict: 'E'
};
function link(scope, element, attrs) {
scope.tooltipContentValue = false;
scope.keyupevt = function() {
if (event.keyCode === 27) {
scope.tooltipContentValue = true;
}
}
}
return directive;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="ExampleApp">
<div ng-controller="ExampleController">
<div style="text-align:center;">
<a href="" ng-keyup="keyupevt()">
<ehub-tooltip>Hello i am here, and i am her to stay</ehub-tooltip>over here</a>
<a href="" ng-keyup="keyupevt()">
<ehub-tooltip>Be nice to people on your way up and they will be nice to you on your way down</ehub-tooltip>click me</a>
</div>
</div>
</div>
I created a custom directive for bootstrap alerts. My alerts display fine (hard code and data bind). Based on the alert type, I want display a unique header into my alert message based on the returned scope values (success, info, warning, danger). Currently I'm passing the type into <h1> but I don't want those values, they need to be custom.
<!-- data binding example -->
<trux-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</trux-alert>
<!-- hard coded example -->
<trux-alert close="close" type="warning">This is an important warning message!</trux-alert>
Inside my directive, the scope is isolated using scope: '#' (one-way)
.directive('truxAlert', function() {
return {
scope: {
type: '#',
close: '&'
},
replace: true,
transclude: true,
template:
'<div class="alert" '+
'ng-class="[\'alert-\' + (type || \'warning\'), closeable ? \'alert-dismissible\' : null]" '+
'role="alert">'+
'<button ng-show="closeable" '+
'type="button" class="close" '+
'ng-click="close({$event: $event})" '+
'data-dismiss="alert" aria-label="Close">'+
'<span aria-hidden="true">×</span>'+
'<span class="sr-only">Close</span>'+
'</button>'+
'<h1>{{type}}</h1>'+
'<div ng-transclude></div>'+
'</div>',
link: function (scope, element, attrs) {}
}
});
This would be easier if all my values were pulled through data binding, but I need to allow for manual hard coded option. I know with one-way isolated scopes '#' I can't change these values though DOM manipulation. I can't use '=' or '&' for two-way because the values are strings.
How do I solve for this problem?
My recommendation is have one attribute for the control the open/closed state of the directive's alert and another attribute for the dismiss handler function.
angular.module("myApp").directive('truxAlert', function() {
return {
scope: {
type: '#',
dismissHandler: '&',
title: '#',
open: '='
},
replace: true,
transclude: true,
template:
'<div class="alert" ng-show="open" '+
'ng-class="[\'alert-\' + type]" '+
'role="type">'+
'<button type="button" class="close" '+
'ng-click="truxClose($event)" '+
'data-dismiss="alert" aria-label="Close">'+
'<span aria-hidden="true">×</span>'+
'<span class="sr-only">Close</span>'+
'</button>'+
'<h1>{{title+" "+type}}</h1>'+
'<div ng-transclude></div>'+
'</div>',
link: function (scope, element, attrs) {
console.log("truxAlert linking");
if (!scope.type) { scope.type="warning" }
scope.truxClose = function(event) {
console.log("truxClose "+event);
if (attrs.dismissHandler) {
scope.dismissHandler({$event: event});
return;
}
scope.open = false;
};
}
};
});
The linking function of the directive determines if the dismiss-handler attribute exists and either invokes the dismiss handler or directly closes the alert.
The DEMO PLNKR show the directive being used both with an ng-repeat directive and in a standalone manner.
Maybe, i don't understand you question.
You want to do so jsfiddle?
<form name="ExampleForm" id="ExampleForm">
<span simple="{{val}}">{{val}} - value from data-binding </span>
<br>
<span simple="valueTwo">valueTwo - hard code value</span>
</form>
And js controller
.controller('ExampleController', function($scope, $rootScope, $alert) {
$scope.val = "valueOne";})
And js directive
.directive('simple', function() {
return {
restrinct: 'A',
scope: {
simple: "#"
},
link: function(scope) {
console.log(scope.simple, typeof(scope.simple));
}
}})
UPDATED
angular.module('ExampleApp', ['use', 'ngMessages'])
.controller('ExampleOneController', function($scope) {
$scope.val = "valueOne";
$scope.$on('pass.from.directive', function(event, value) {
$scope.valFromDirective = value;
});
})
.controller('ExampleTwoController', function($scope) {
$scope.val = "valueTwo";
$scope.$on('pass.from.directive', function(event, value) {
$scope.valFromDirective = value;
});
})
.controller('ExampleThreeController', function($scope) {
$scope.val = "valueThree";
$scope.$on('pass.from.directive', function(event, value) {
$scope.valFromDirective = value;
});
})
.directive('simple', function($interval) {
return {
restrinct: 'A',
scope: {
simple: "#"
},
link: function(scope) {
var i = 0;
$interval(function() {
i++;
scope.$emit('pass.from.directive', scope.simple + i);
}, 1000);
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular-messages.min.js"></script>
<script src="https://cdn.rawgit.com/Stepan-Kasyanenko/use-form-error/master/src/use-form-error.js"></script>
<div ng-app="ExampleApp">
<div ng-controller="ExampleOneController">
<h3>
ExampleOneController
</h3>
<form name="ExampleForm" id="ExampleForm">
<div simple="{{val}}">{{val}} - value from scope </div>
<div>{{valFromDirective}} - value from directive </div>
</form>
</div>
<div ng-controller="ExampleTwoController">
<h3>
ExampleTwoController
</h3>
<form name="ExampleForm" id="ExampleForm">
<div simple="{{val}}">{{val}} - value from scope </div>
<div>{{valFromDirective}} - value from directive </div>
</form>
</div>
<div ng-controller="ExampleThreeController">
<h3>
ExampleThreeController
</h3>
<form name="ExampleForm" id="ExampleForm">
<div simple="{{val}}">{{val}} - value from scope </div>
<div>{{valFromDirective}} - value from directive </div>
</form>
</div>
</div>
I want to make a reusable modal-window-directive (i found a pair modal-window examples, but that's not what i want, or they do not work properly).
It should look something like this:
(1) title-, body-text and function-name (for the function, which is binded to the action-button in the template) are defined directly within directive
<my-modal
modal-title="Modal Window Title"
modal-body="This is my Modal Window Body"
click-main-html-action-button="myControllerActionFunction()">
</my-modal>
(2) The HTML-template for modal window must pick up the corresponding values
<div class="panel-default">
<div class="panel-heading">
{{title}}
</div>
<div class="panel-body">
<div class="row">
<div class="form-group col-sm-12">**{{body}}**</div>
</div>
<div class="col-sm-12 text-center">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-ng-click="templatestartaction(); $event.stopPropagation()">Start action</button>
</div>
</div>
</div>
(3) Button-function for modal window must be defined In the controller
app.controller('controller', function($scope) {
scope.title = "";
scope.body = "";
$scope.myControllerActionFunction = function () {
console.log('myControllerActionFunction', title);
};
})
The question is: how can I read the attrs values from (1)
(modal-title = "Modal Window Title", etc.) and the function name
(click-main-html-button action = "myControllerActionFunction ()") and
pass it to the model and function definition in the controller
(3), so that it is displayed in the html-template (2)?
I can read the attributes from (1), but I do not know how i pass it in controller.
(4)
app.directive('myModal', function () {
return {
transclude: true,
restrict: 'E',
//template: '<div ng-transclude></div>',
templateUrl: "directives/modal/view/modalWindowTemplate.html"
scope: {
templatestartaction: '&clickMainHtmlActionButton'
},
link: function (scope, element, attrs) {
scope.title = attrs.modalTitle;
scope.body = attrs.modalBody;
}
};
});
You need to define title and body in the controller like
$scope.modalProps = {
title: 'My modal title',
body: 'My modal body'
};
Use this in the html
<my-modal
modal-title="modalProps.title"
modal-body="modalProps.body"
click-main-html-action-button="myControllerActionFunction()">
</my-modal>
And define it for the directive
app.directive('myModal', function () {
return {
transclude: true,
restrict: 'E',
//template: '<div ng-transclude></div>',
templateUrl: "directives/modal/view/modalWindowTemplate.html"
scope: {
templatestartaction: '&clickMainHtmlActionButton'
title: '=modalTitle',
body: '=modalBody'
},
...
I'm using the angular-google-maps library in my project. I have used a directive to load a custom google maps menu. The goal is to obviously reuse the directive. In the menu are a couple of buttons which when clicked should all carry out a function. I'm still trying to get my head around on how to do that, so here is my problem:
I would like to pan the map to its original position when the button "Home" is clicked. Normally that is just done with ng-click and the function is placed within the scope of the controller. With the directive I'm confused. Where should I place the "home()" function? Directive? Directive controller? Controller? I hope this makes any sense?!?!
HTML:
<div class="map_canvas">
<google-map center="map.center" zoom="map.zoom" draggable="true">
<marker ng-repeat="m in map.markers" coords="m" icon="m.icon" click="onMarkerClicked(m)">
<marker-label content="m.name" anchor="50 0" class="marker-labels"/>
<window ng-cloak coords="map.center" isIconVisibleOnClick="false" options="map.infowindows.options">
<p>This is an info window at {{ m.latitude | number:4 }}, {{ m.longitude | number:4 }}!</p>
<p class="muted">My marker will stay open when the window is popped up!</p>
</window>
</marker>
<map-custom-control position="google.maps.ControlPosition.TOP_CENTER" control-template="../templates/gmaps/main_menu.html" control-click=""></map-custom-control>
</google-map>
</div>
Template:
<div class="gmaps-menu">
<div class="gmaps-row">
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_020_home.png" ng-click="home()"></button>
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_349_fullscreen.png"></button>
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_096_vector_path_polygon.png"></button>
<button type="button" class="btn btn-default"><img class="glyphicon-custom" src="../img/icons/glyphicons/glyphicons_030_pencil.png"></button>
</div>
</div>
Directive:
AppDirectives.directive('mapCustomControl', ['$log', '$timeout', '$http', '$templateCache', 'google', 'GMapsLib' ,function ($log, $timeout, $http, $templateCache, google,GMapsLib) {
return {
restrict: 'E',
replace: true,
require: '^googleMap',
link: function(scope,element,attr,mapCtrl){
if (!angular.isDefined(attr.controlTemplate)) {
$log.error('map-custom-control: could not find a valid control-template property!');
return;
}
var templateUrl = attr.controlTemplate;
var position = google.maps.ControlPosition.TOP_CENTER;
if (angular.isDefined(attr.position)) {
var EVAL_IS_OK_WE_CONTROL_THE_INPUT = eval;
position = EVAL_IS_OK_WE_CONTROL_THE_INPUT(attr.position);
}
$timeout(function() {
var map = mapCtrl.getMap();
var controlDiv = document.createElement('div');
controlDiv.style.padding = '5px';
controlDiv.style.width = 'auto';
controlDiv.marginLeft = 'auto';
controlDiv.marginRight = 'auto';
$http.get(templateUrl, {cache: $templateCache})
.success(function(html) {
controlDiv.innerHTML = html;
})
.then(function (/*response*/) {
map.controls[position].push(controlDiv);
if (angular.isDefined(attr.controlClick)) {
google.maps.event.addDomListener(controlDiv, 'click', function() {
scope.$apply(attr.controlClick);
});
}
}
);
});
}
};
}]);
You can pass the scope function that has to be executed on the controller:
HTML
<div ng-app="app" ng-controller="sampleCtrl">
<maps-custom-control click-handler="alertMe()"></maps-custom-control>
</div>
JS
var app = angular.module('app', []);
app.directive('mapsCustomControl', function() {
return {
restrict: 'EA',
replace: true,
scope: {
clickHandler: '&'
},
template: '<div style="width: 100px; height:100px; background-color: red;" ng-click="clickHandler()"></div>'
};
});
app.controller('sampleCtrl', function ($scope) {
$scope.alertMe = function () {
window.alert('Refresh gMaps control');
};
});
Since we pass the alertMe function, this is the function that will get executed, I hope this makes sense?
Fiddle
A small remark on your code, it would be better if you get the template as follows:
app.directive('..', function() {
return {
template: '<div ng-include="getTemplate()"></div>',
link: function(scope, element, attr) {
scope.getTemplate = function() {
return this.attr.controlTemplate;
}
}
};
});
This way you don't need to do any strange ajax calls. Just add all the mark-up in your template and include it. don't make it necessary hard :-)