Custom AngularJS directive with xeditable: doesn't work first time, but works afterward - angularjs

Building an AngularJS application, I'm using xeditable to allow the user to edit a table, row per row (as described here: xeditable).
The Edit, Remove and Cancel button logic being a bit cumbersome to implement in each table, I created a custom directive: sen-edit-row.
The logic of the buttons in the custom directive works well... except in one case.
When the user clicks [Add], a row is added (just like in xeditable's example), and the new row is immediately editable. Or it should!
The very first time, the row is not editable. The input fields are not created by xeditable. The buttons of my own directive still work.
But the most bizarre thing is: the 2nd time the user clicks [Add], it just works. And then it keeps working!
(this is driving me nuts)
In the HTML, the directive is called this way:
<tr ng-repeat="access in denyAccessTable">
<td class="col-md-4"><div editable-text="access.user" e-form="rowForm">{{ access.user }}</div></td>
<td class="col-md-4"><div editable-text="access.host" e-form="rowForm">{{ access.host }}</div></td>
<td class="col-md-2"><div editable-text="access.mode" e-form="rowForm">{{ access.mode }}</div></td>
<td class="col-md-2"><div sen-edit-row save="saveDenyAccess()" remove="removeDenyAccess($index)" shown="access == newDenyAccess"></div></td>
</tr>
In the JS, the directive is defined as:
angular.module("app", ["xeditable"]).directive("senEditRow", [function() {
return {
restrict: "A",
templateUrl: "sen-edit-row.html",
scope: {
save: "&",
remove: "&",
shown: "="
},
link: function(scope, element, attrs) {
}
}
}])
And the template of the directive:
<div style="white-space: nowrap">
<form class="form-inline" editable-form name="rowForm" onaftersave="save()" ng-show="rowForm.$visible" shown="shown">
<button type="submit" class="btn btn-primary" ng-disabled="rowForm.$waiting">Save</button>
<button type="button" class="btn btn-link" ng-disabled="rowForm.$waiting" ng-click="shown ? remove() : rowForm.$cancel()">Cancel</button>
</form>
<!-- Buttons to edit and remove -->
<div ng-hide="rowForm.$visible">
<span class="glyphicon glyphicon-edit" style="margin-right: 5px;"></span>Edit
<span class="glyphicon glyphicon-trash" style="margin-left: 10px; margin-right: 5px;"></span>Remove
<button class="btn btn-danger" ng-click="remove()" ng-show="showDeleteButton">Remove</button>
<button class="btn btn-link" ng-click="showDeleteButton = false" ng-show="showDeleteButton">Cancel</button>
</div>
</div>
I also created a Plunker that shows evidence of this behavior.
Question: How do I make my directive and xeditable work consistently, including the first time, and activate immediately when a row is added to the table?
Thank you!

Related

Angularjs required validator for array in model

I have an array of 'bikes' that is required to be populated in my form (minimum length of 1). In my controller I have an empty array 'this.bikes = [];'
I have some controls which add and remove bikes from the array.
addBike(bike){
this.bikes.push(bike);
this.currentBike = null;
}
removeBike(bike){
this.bikes = this.bikes.filter((b) => bike != b);
}
How do I apply form validation on the array itself so that my form shows invalid if the array is empty (Something like 'if ($ctrl.bikes.length == 0) $ctrl.form.bikes.$valid = false)?
<label for="bike-make-model">Enter make and model of bike</label>
<div class="input-group p-relative">
<input name="bikeMakeModel" type="text" ng-model="$ctrl.currentBike" class="form-control" id="bike-make-model">
<span class="input-group-btn ">
<button class="btn btn-default" ng-click="$ctrl.addBike($ctrl.currentBike)" ng-disabled="!$ctrl.currentBike">ADD</button>
</span>
</div>
</div>
<div class="list-group" ng-show="$ctrl.bikes.length > 0">
<ul>
<li class="list-group-item" ng-repeat="bike in $ctrl.bikes">
<span>{{bike}} </span>
<i ng-click="$ctrl.removeBike(bike)" class="pointer pull-right far fa-trash-alt"/>
</li>
</ul>
</div>
I want to use it for to disable my submit button using the ng-disabled directive.
<button class="btn btn-primary" ng-disabled="!$ctrl.form.$valid" ng-click="$ctrl.continue()">Next</button>
Have you tried
<button class="btn btn-primary" ng-disabled="!$ctrl.form.$valid || $ctrl.bikes.length==0" ng-click="$ctrl.continue()">Next</button>
or you could add a variable in your script to monitor the length of the array. Say
addBike(bike){
this.bikes.push(bike);
this.currentBike = null;
this.bikesArrayLength = this.bikes.length;
}
removeBike(bike){
this.bikes = this.bikes.filter((b) => bike != b);
this.bikesArrayLength = this.bikes.length;
}
and in your button do
<button class="btn btn-primary" ng-disabled="!$ctrl.form.$valid || $ctrl.bikesArrayLength==0" ng-click="$ctrl.continue()">Next</button>
You can use directive for this purpose. Make a directive and in link function define the logic , if the array is empty make form invalid and it won't submit, your code is short that is why , i can't do it for you.
This is angular way to solve your problem. All i can do now is provide the logic or give you idea how this will go down.
.directive("dirName",function(){
return {
require: "ngModel",
scope: {
confirmArrayLength: "="
},
link: function(scope, element, attributes, modelVal) {
modelVal.$validators.dirName= function(val) {
return "your logic to check if array is empty or not!"
};
// and $watch function will validate and invalidate on the basis of return value of above function
scope.$watch("confirmArrayLength", function() {
modelVal.$validate();
});
}//link ends here
};//return ends here})
If this is the solution of your problem then accept this answer so that this thread can be close, thanks. And if you need any further assistance then feel free to ask.

angular bootstrap html popover not being displayed

I'm trying to use the uib-popover-html directive and it doesn't display. If I use the uib-popover or uib-popover-template directive it displays correctly. Am I missing something?
This works
$scope.dynamicPopover = {
content: 'Hello, World!',
templateUrl: 'app/login/popover.html',
title: 'Title'
};
<!-- popover.html -->
<div>
<b style="color: red">I can</b> have <div class="label label-success">HTML</div> content
</div>
<!-- /popover.html -->
<button uib-popover="I appeared on mouse enter!" popover-trigger="mouseenter" type="button" class="btn btn-default">Mouseenter</button>
<button uib-popover-template="dynamicPopover.templateUrl" popover-title="{{dynamicPopover.title}}" type="button" class="btn btn-default">Popover With Template</button>
This doesn't
<button uib-popover-html="htmlPopover" class="btn btn-default">HTML Popover</button>
Try using $sce (Strict Contextual Escaping) to show that the HTML is safe:
$htmlPopover = $sce.trustAsHtml('<div>Popover Content</div>');
This feature exists to avoid user's from inputting HTML that could be nefarious into a form, etc.
References: https://docs.angularjs.org/api/ng/service/$sce

Angular Bootstrap UI: Input Fields in Tab-Heading

Again I got a Problem with Angular Bootstrap UI Tabs.
Short description of my problem:
I want the user to create different pages with different titles. After a page is created, I create a new tab with the pagetitle and user can add content. That works fine so far.
Now, in the uib-tab-heading I have an option to edit the page-title
<uib-tab-heading>
<div class="pull-left">
<span data-ng-if="!editable[$index]">{{title}}</span>
<input data-ng-if="editable[$index]" data-ng-model="titles[$index]">
</div>
<div class="pull-right">
<button data-ng-if="!editable[$index]" data-ng-click="edit($index)"><span class="glyphicon glyphicon-wrench"></span></button>
<button data-ng-if="editable[$index]" data-ng-click="save($index)"><span style="color:green;" class="glyphicon glyphicon-ok"></span></button>
</div>
<div style="clear:both;"></div>
</uib-tab-heading>
The button action sets a variable so the input field in the tab appears. That works so far.
But in the input field I only can edit one single letter, then, after input, the input field looses its focus and the tab is changed in a random way.
Is there a common method to disable keyboard interaction with the tabs so I can change the value of the input field without getting the tab changed?
One option is just to force the focus back to the input element whenever it is modified.
See fiddle: https://jsfiddle.net/masa671/2mq3106a/
Markup:
<input data-ng-if="editable[$index]" ng-model="titles[$index]" focus-me>
JavaScript:
.directive('focusMe', function () {
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel) {
scope.$watch(function () {
return ngModel.$modelValue;
}, function() {
elem[0].focus();
});
}
};
});
Thanks for your answer!
I modified your fiddle a bit to fit my code.
See fiddle: https://jsfiddle.net/2mq3106a/7/
<div ng-controller="MyCtrl">
<uib-tabset>
<uib-tab class="tabcontent-bordered" data-ng-repeat="pageTitle in titles">
<uib-tab-heading>
<div class="pull-left">
<span data-ng-if="!editable[$index]">{{pageTitle}}</span>
<input id="$index" data-ng-if="editable[$index]" ng-model="titles[$index]" focus-me>
</div>
<div class="pull-right">
<button data-ng-if="!editable[$index]" data-ng-click="edit($index)"><span class="glyphicon glyphicon-wrench"></span></button>
<button data-ng-if="editable[$index]" data-ng-click="save($index)"><span style="color:green;" class="glyphicon glyphicon-ok"></span></button>
</div>
<div style="clear:both;"></div>
</uib-tab-heading>
I am on Tab {{$index}}
</uib-tab>
</uib-tabset>
</div>
I do not lose the focus on the input field, thats works so far, but there is still an other problem.
As you may can see in the fiddle if i change input value of the input field, the other tab gets active state and so the content of the other tab is shown.

Bootstrap and angular - pass object to modal

I create table using bootstrap and angular use object to display data. I want to open popup window to change object property by clicking row (and use angular binding in modal to work with object).
So my issue that i doesn't understand how to pass object from table to modal.
I found many examples with passing values and changing html on fly but in my case i want to use angular for binding and want to pass reference to object.
Sample of table:
<table class="table table-condensed table-bordered">
<thead>...</thead>
<tbody>
<tr ng-repeat="property in properties" data-toggle="modal" data-id="property" data-target="#editPropertyModal">
<td>{{property.name}}<td/>
<td>{{property.value}}<td/>
<td>...<td/>
</tr>
</tbody>
</table>
So by clicking this row i want to open modal and pass there property object and use controls like input, combobox to bind values via angular.
My sample of modal:
<div class="modal fade" id="editPropertyModal" tabindex="-1" role="dialog" aria-labelledby="editPropertyModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="editPropertyModalLabel">Property details</h4>
</div>
<div class="modal-body">
{{currentProperty.name}} - {{currentProperty.value}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
My try to pass property via javascript:
$(document).on("click", ".open-editPropertyModal", function (e) {
e.preventDefault();
var _self = $(this);
var property = _self.data('id');
$("#currentProperty").val(property);
$(_self.attr('href')).modal('show');
});
Doesn't work.
You can use this to do that. It really helped me.
Default Bootstrap JavaScript is not working well with angular, because it makes changes in DOM model outside AngularJS. Please consider using native directives for bootstrap, like these http://mgcrea.github.io/angular-strap/
Also, for maximum simplicity, in many cases, you can use something like this plugin https://github.com/cgross/angular-prompt. It depends on bootstrap and gives you simple API based on promises.
prompt({
title: 'Give me a name',
message: 'What would you like to name it?',
input: true,
label: 'Name',
value: 'Current name',
}).then(function(name){
//name contains new value
});
Callback passed to then will be executed after clicking "OK" in popup.

Batch trigger Angular Bootstrap button click events

I have a large Angular page with 100+ rows of data, each with a button in the final column which sets a Bootstrap radiomodel based on which button you click.
//....
<tr>
<td>
data1
</td>
<td>
data2
</td>
<td>
<div ng-controller="pickSource" class="btn-group " role="group" aria-label="...">
<label class="btn btn-info btn-xs pickSourceButton takeXxxBtn"
ng-model="radioModel"
btn-radio="'yyy'" >
Approve
</label>
<label class="btn btn-warning btn-xs pickSourceButton takeYyyBtn"
ng-model="radioModel"
btn-radio="'yyy'" >
Reject
</label>
<label class="btn btn-danger btn-xs pickSourceButton noActionBtn"
ng-model="radioModel"
btn-radio="'noAction'" >
Hold
</label>
</div>
</td>
</tr>
//... many of these, the picksource block is actually a directive but i simpified here
Button is like this: (approve|reject|hold)
Controller for this button is like this:
controller('pickSource', function ($scope, $log) {
$scope.radioModel = "noAction";
$scope.$watch('radioModel', function(newValue, oldValue) {
if (newValue !== oldValue) {
$log.log('emitting event');
//even if i remove this below it is slow
$scope.$emit('pickSourceButtonDidChange',someDATA);
}
});
});
I have a batch operation button which will select the same value (i.e. select source for all the rows) for every row.
Currently I am triggering the events like so:
//inside bootstrap dropdown
if(source == "xxx"){
el = document.getElementsByClassName('takexxxBtn');
}
else if (source == "yyy"){
el = document.getElementsByClassName('takeyyyBtn');
}
else{
el = document.getElementsByClassName('noActionBtn');
}
$timeout(function() {
angular.element(el).triggerHandler('click');
}, 0);
However, I am getting really poor performance from this. I imagine this is because the click events each have quite a expensive series of actions (I even took out all the code that is triggered when the button is clicked and still got the same performance hit), but I am wondering if there is any alternative. I cannot simply change the underlying data that the trigger executes because I need the UI to update as well (I need button to look like it's clicked, etc.)
Is there any way to do this?
Using Matthew Berg's (idk how to link to your profile :S ) solution, I emitted an event from the batch action button controller to the parent controller, caught the event in the parent controller and then broadcast the model changes down to all the bootstrap button controllers. This worked excellently.
Thanks Matt

Resources