Angular + Bootstrap Toggling Radio Buttons - angularjs

I'm trying to create a set of radio buttons that I want to exhibit toggling behavior (i.e., the selected button should be highlighted). I'm using the following partial template:
<h3>{{fullAddress()}}</h3>
<h4 class="control-label">Result of Visit</h4>
<pre>{{visit}}</pre>
<div class="btn-group" data-toggle="visitState">
<div ng-repeat="option in visitOptions()" class="btn btn-default" ng-value="option.Value" ng-model="$parent.visit">
{{option.Label}}
</div>
</div>
The controller is quite simple:
app.controller("visitCtrl", function($scope, dataContext) {
var _visit = "NotHome";
$scope.visit = "NotHome";
$scope.visitOptions = function() {
return dataContext.visitOptions();
};
$scope.fullAddress = function() {
var home = dataContext.home();
var pin = dataContext.pin();
if( home.Unit == "" ) return pin.StreetAddress;
return pin.StreetAddress + " #" + home.Unit;
};
});
dataContext.visitOptions() just returns an array of {Label, Value} objects.
As things stand, there is no toggling behavior. Then again, the model value (visit) doesn't update when you click any of the buttons, either :).

For the benefit of others, there were two problems with my code:
I was including jQuery, which conflicts with the toggling behavior. I had to remove jQuery and include ui-bootstrap to replace the event-driven functionality bootstrap depends uses.
Tying ng-model to a "root-level" property (i.e., $scope.visit) keeps the auto-updating/two-way data binding functionality of angular from working. I had to tie ng-model to visit.result instead:
$scope.visit = {
result: "NotHome",
hadQuestion: false,
notes: null,
};
The final markup was pretty simple:
<div class="btn-group" data-toggle="visitState">
<div ng-repeat="option in visitOptions()" class="btn btn-primary" btn-radio="option.Value" ng-model="visit.result">
{{option.Label}}
</div>
</div>

Related

Is it possible to change angular bootstrap uib-dropdown templateUrl dynamically?

I want to change the uib-dropdown template dynamically when the user clicks one of its <li>, just like he could "navigate" within that dropdown.
I tried to make it via templateUrl, but nor the ng-templates nor standalone partials can successfully change the dropdown template dynamically, just like this plunkr demonstrates.
My goal is to create a faceted navigation via this dropdown to build query visually, as seen on Fieldbook's Sprint tracker (account required), which is something really like Pure Angular Advanced Searchbox, but I'm having overwhelming issues using this library.
Is this possible to achieve using just AngularJS and angular-bootstrap?
EDIT: You should assign the value of template-url using a controller var which changes as the user select any of the options, then you "repaint" the component, this way the "new" dropdown is "repainted" with the new template.
Yes, it's possible according to the official documentation, though I've never done this before.
You can specify a uib-dropdown-menu settings called template-url.
According to the docs the
default value is none
and
you may specify a template for the dropdown menu
Demo(try the last one):
http://plnkr.co/edit/1yLmarsQFDzcLd0e8Afu?p=preview
How to get it to work?
Based on your plunkr, you should change
<div class="input-group" uib-dropdown auto-close="disabled">
<input type="text" class="form-control" placeholder="Click to start a visual query search..." autocomplete="off" uib-dropdown-toggle/>
<ul class="dropdown-menu" role="menu" ng-if="ctrl.dropdownReady" uib-dropdown-menu template-url="{{ctrl.dropdownTemplateFour}}">
</ul>
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
</button>
</span>
</div>
to
<div class="input-group" uib-dropdown auto-close="disabled" ng-if="ctrl.dropdownReady">
<input type="text" class="form-control" placeholder="Click to start a visual query search..." autocomplete="off" uib-dropdown-toggle/>
<ul class="dropdown-menu" role="menu" uib-dropdown-menu template-url="{{ctrl.dropdownTemplateFour}}">
</ul>
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
</button>
</span>
</div>
In which the ng-if="ctrl.dropdownReady" is moved to the div.input-group.
And change
vm.dropdownReady = false;
console.log('vm.dropdownReady =', vm.dropdownReady, ' partial = ', partial);
switch (template) {
case 'word':
partial ? vm.dropdownTemplateFour = 'word-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'word-dropdown-dom.html';
break;
case 'main':
partial ? vm.dropdownTemplateFour = 'main-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'main-dropdown-dom.html';
break;
}
vm.dropdownReady = true;
to
vm.dropdownReady = false;
console.log('vm.dropdownReady =', vm.dropdownReady, ' partial = ', partial);
switch (template) {
case 'word':
partial ? vm.dropdownTemplateFour = 'word-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'word-dropdown-dom.html';
break;
case 'main':
partial ? vm.dropdownTemplateFour = 'main-dropdown-dom.template.html' : vm.dropdownTemplateThree = 'main-dropdown-dom.html';
break;
}
$timeout(function(){
vm.dropdownReady = true;
});
Which has a $timeout wrap the vm.dropdownReady = true;. And you should inject the $timeout by hand;
Keep the menu open
According to the documentation, we can choose the initial state of drop menu with is-open attr. And we can listen the toggle event with on-toggle attr. So if we want to keep the menu open after user clicking the input, we should set the attributes of uib-dropdown like this:
<div class="input-group" uib-dropdown auto-close="disabled" ng-if="ctrl.dropdownReady" is-open="ctrl.open" on-toggle="ctrl.toggled(open)">
And in controller:
vm.toggled = function (open) {
// the parameter `open` is maintained by *angular-ui/bootstrap*
vm.open=open;//we don't need to init the `open` attr, since it's undefined at beginning
}
With these things done, once the menu is open, it doesn't close without user clicking the input again.
Why?
Let's check this snippet:
$templateRequest(self.dropdownMenuTemplateUrl)
.then(function(tplContent) {
templateScope = scope.$new();
$compile(tplContent.trim())(templateScope, function(dropdownElement) {
var newEl = dropdownElement;
self.dropdownMenu.replaceWith(newEl);//important
self.dropdownMenu = newEl;
$document.on('keydown', uibDropdownService.keybindFilter);
});
});
The snippet above shows how does angular-ui/bootstrap use the template-url attr to retrive template and take it into effect. It replaces the original ul element with a newly created element. That's why the uib-dropdown-menu and template-url is missing after clicking the ul. Since they don't exist, you can't change the template-url with angular binding anymore.
The reason executing vm.dropdownReady = true; immediately after the vm.dropdownReady = false; doesn't work is that angular have no chance to dectect this change and execute the "ng-if" to actually remove the dom. You must make the toggling of vm.dropdownReady asynchronous to give angular a chance to achieve this.

Angularjs 1.5 replace directive element html

I want to built an ng-repeat with dynamic buttons that change according to $state.params so I wrapped them in a directive.
link:function(scope,element,attrs) {
$rootScope.$watch('params.source',function() {
var link = scope.link;
var name = scope.name;
var href = $state.href('home',{source:link});
if($state.params.source.indexOf(scope.link) == -1) {
var template = '<a href="'+href+'" class="btn
btn-default">'+name+'</a>';
}
else template = '<a href="'+href+'" class="btn
btn-default">'+name+' what!?</a>';
el = $compile(template)(scope);
element.replaceWith(el);
});
}
But after the the first change of params, element is forgotten and answers with Cannot read property 'replaceChild' of null
How can I replace element?
Edit: I need to swap the whole Element for sowmething else in final product, that's why I can't use ng-show or ng-if
Thanks!
You may be better off using ng-hide or ng-show in your html. Maybe something like this:
<div ng-repeat="yourArray as item">
<a href="{{item.href}}" class="btn
btn-default">{{item.name}} <section ng-show="conditionToShow">what!?</section></a>';
</div>
This is a bit cleaner of a solution and it keeps you from having to add a watch. Watches are expensive and you should avoid them if at all possible.
Edit:
<div ng-repeat="yourArray as item">
<div class="singleBtn" ng-show="conditionToShow" >
{{item.name}}
</div>
<div class="multiBtn" ng-hide="conditionToShow">
<a //rest of multi btn code
</div>
</div>
Because of the fact that Angular provides both ng-show and ng-hide directives this allows you to control the presentation of these elements based on just one conditional.(i.e. your state.params)
When it is true, it will show just one btn and when it's false it will show multiple buttons. If the logic needs to be reversed, just switch ng-show and ng-hide.
Hopefully I understood your need better this time.

Using href Within ng-repeat

I am using AngularFire and Firebase to store objects which contain a title and a url. I call the object list. I want to:
ng-repeat through my array of objects.
Use the list.url to download the URL's contents. All of the URL's are links to either a .js or a .css file.
Apply the download link/function to a button that is nested inside an input-group (Bootstrap 3.3.6).
All of this to be done within a routed view (ui-router)
What the buttons look like within the input-group
Here's my div. It's nested within a routed view. I am working on the first button within input-group-btn.
<div class="list-group">
<a class="list-group-item" ng-repeat="(id, link) in links | filter: search | orderBy: '-downloadCount' | limitTo: 5">
<h4 class="align-left list-group-item-heading">{{ link.name }}</h4>
<h4 class="align-right list-group-item-heading">{{ link.downloadCount }}</h4>
<br>
<br>
<div class="list-group-item-text">
<div class="input-group">
<input type="text" class="form-control" readonly value="{{ link.url }}" />
<div class="input-group-btn">
// *** IT'S THIS FIRST LINK THAT ISN'T WORKING ***
<button class="btn btn-default" ng-href="{{link.url}}"><i class="glyphicon glyphicon-download"></i></button>
<button class="btn btn-primary" ng-click="copyToClipboard(link)" tooltip-placement="bottom" uib-tooltip="Copy"><i class="glyphicon glyphicon-copy"></i></button>
</div>
</div>
</div>
</a>
</div>
When I use anchor tags instead of button tags, the ng-repeat goes crazy, the links get all kinds of messed up, formatting goes haywire, and there ends up being only one copy of the button that doesn't even work.
I'm not sure if I'm supposed to interpolate the link that ng-href is referring to, but I've tried it with and without and I still don't have anything.
Here's my JS to answer a comment.
var ref = new Firebase('https://myApp.firebaseio.com/links');
$scope.links = $firebaseArray(ref);
$scope.addLink = function () {
$scope.links.$add({
name: $scope.newLinkName,
url: $scope.newLinkUrl,
downloadCount: 1
});
$log.info($scope.newLinkName + ' added to database');
$scope.newLinkName = '';
$scope.newLinkUrl = '';
};
$scope.incrementDownloadCount = function (link) {
link.downloadCount += 1;
var tempCount = link.downloadCount;
var currentRef = new Firebase('http://myApp.firebaseio.com/links/' + link.$id);
// update the downloadCount
currentRef.update({ downloadCount: tempCount });
// update priority for sorting
currentRef.setPriority(tempCount);
$log.info(link.name + ' downloadCount changed to ' + link.downloadCount);
};
// Possibly relevant includes
ui.router
ui.bootstrap
firebase

Angularjs Dropdown closes when $location.search parameters are changed

I'm building an ecommerce site. I have a directive called "horizontalShoppingByCategoriesLayout" that watches the current filter criteria (to filter products being returned) and as the user changes those filter criteria the directive sets the search parameters $location.search(..).
...
filterParameters['searchtext'] = $scope.searchText;
filterParameters['order'] = searchorder;
filterParameters['page'] = page;
filterParameters['limit'] = limit;
$location.search(filterParameters);
On that same directive my code is set up to watch the url, and if it changes (due to changes in filter criteria) then to do a search to return products.
$scope.$watch(function () { return $location.url(); }, function (url){
if (url){
$timeout(function() {
getProducts();
});
}
});
The filter criteria are defined in a directive called "filterCriteriaVarietyCategoriesHorizontal" that is defined in the template of "horizontalShoppingByCategoriesLayout".
<div class="pull-leftt" ng-if='numberProducts != undefined && numberProducts > 0'>
<filter-criteria-horizontal execute-criteria-search=$parent.executeCriteriaSearch category-id=$parent.categoryId search-criteria-list=$parent.searchCriteriaList></filter-criteria-horizontal>
</div>
When a user hovers over the filter criteria category button, this triggers an angular event that causes an Angular-UI Bootstrap dropdown to drop down and show the options for that filter criteria category. The user can then click on the filter criteria option, which "horizontalShoppingByCategoriesLayout" detects and then calls a change on the $location search parameters (which changes the url and a webservice call for products is initiated).
The filter criteria options in the dropdown are a set of checkboxes. My problem is when the user clicks on one of these checkboxes, it causes the dropdown to close, which should only happen when the user hovers off of the button that initiated the dropdown opening. I already fixed the problem where the clicking on the checkbox it's self causes the dropdown to close by calling ng-click="$event.stopPropagation()" on the checkbox. Here is my code for the filtercriteria category, dropdown, and checkboxes.
<div class="coll-md-12" ng-mouseleave="closeAll()">
<div class="btn-group" dropdown is-open="status.isopen">
<button ng-mouseleave="close()" ng-mouseenter="open()" type="button" ng-class="{'filter-criteria-variety-category-name-hover': filterCriteriaCategoryActive}" class="btn btn-link dropdown-toggle filter-criteria-variety-category-name" dropdown-toggle ng-disabled="disabled">
{{searchCriteria.criteriaName}}<span class="caret"></span>
</button>
<div class="dropdown-menu">
<div class="search-criteria-values" ng-mouseleave="close()" ng-mouseenter="keepOpen()">
<div ng-if="searchCriteria.imageBasedFilter == true">
<filter-criteria-options-visual filter-criteria-options=$parent.searchCriteria.criteriaOptionValues></filter-criteria-options-visual>
</div>
<div ng-if="searchCriteria.imageBasedFilter == false">
<div class="col-md-12 no-padding" ng-repeat="searchCriteriaOption in searchCriteria.criteriaOptionValues">
<div class="pull-left filter-criteria-checkbox-item">{{searchCriteriaOption.criteriaOption}} <input ng-click="$event.stopPropagation()" id="{{searchCriteriaOption.criteriaOption}}" type="checkbox" ng-model="searchCriteriaOption.checked"/></div>
</div>
</div>
</div>
</div>
</div>
I'm sure that it is adding search parameters to the url with $location.search(...) that is causing the dropdown to close because if I comment out the line that adds the (filter criteria to the) search parameters to the url in the directive "horizontalShoppingByCategoriesLayout", then the dropdown does not close.
...
filterParameters['searchtext'] = $scope.searchText;
filterParameters['order'] = searchorder;
filterParameters['page'] = page;
filterParameters['limit'] = limit;
//$location.search(filterParameters);
With the checkboxes in the dropdown I was able to stop the dropdown from closing by calling stopPropagation() on the click event, but for a change in the $location search parameters I don't know how to stopPropogation for that change.
Does anyone have an idea how to cause the dropdown to not close when changes are made to the $location.search parameters?
I had the same issue then I looked into the source code of angular boostrap ui and found:
$scope.$on('$locationChangeSuccess', function() {
scope.isOpen = false;
});
And a hacky solution would be remove the listener
$scope.$$listeners.$locationChangeSuccess = [];
in your dropdown controller
The solution from user3217868 worked well for me but I had to execute the code once the document was fully loaded like on my code below.
angular.element(document).ready(function () {
$scope.$$listeners.$locationChangeSuccess = [];
});
Posting here in case it may be helpful to someone else.

duplicates in a repeater are not allowed angular

I'm trying to create a form like below, this using ng-repeat directive in angular and it whenever I created a new row complains
"Duplicates in a repeater are not allowed.".
While I understand the solution for this is by putting "track by $index", however it causes another issue, which clicking delete on one row deletes the value of other field. So I suspect that track by index is OK for static text but not input form. So how to use ng-repeat correctly for my case?
My jsfiddle : demo.
Edit : I do aware that json array of object will solve my issue ( because for object angular create $$hashKey ) and already implemented this for most of my other module. But I am actually expecting some fix that can be done without really change my json array of string. Sorry for not being clear.
My current code :
HTML
<div class="row-fluid spacer10">
<a ng-click="addAKA()" class="btn btn-primary spacer5 left30"><i class="icon-plus icon-white"></i> Add New Alias</a>
</div>
<div class="row-fluid spacer10"></div>
<div class="row-fluid spacer5" ng-repeat="item in aliasList track by $index">
<input type="text" class="span6 left30" ng-model="item">
<button class="btn btn-danger" ng-click="deleteAKA($index)">delete</button>
<BR/>
</div>
Javascript
$scope.addAKA = function ()
{
if($scope.aliasList == null)
{
$scope.aliasList = [];
}
$scope.aliasList.push("");
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
$scope.deleteAKA = function (idx)
{
var aka_to_delete = $scope.aliasList[idx];
$scope.aliasList.splice(idx, 1);
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
I would guess this is caused when there are more than one empty strings in the list.
If this is the case, it is caused because any two empty strings are equals in JS and Angular repeater does not allow duplicate values (as clearly stated in the message). This is a valid decision as they have to relate an object in the list with its DOM tree to minimize DOM manipulation.
A solution would be to insert simple objects containing the string in the model:
$scope.addAKA = function () {
...
$scope.aliasList.push({value:""});
...
};
And adjust your template:
<input type="text" class="span6 left30" ng-model="item.value">
Since all new objects are different, your problem should be solved.
See a fiddle where a filter is implemented to transform the model back to a list of strings.
When you type in a new created input, your list stays the same. Angular on any list change will update the view (ng-repeat) and remove all new stored text. Therefore we need to add ng-change to update our list on any input change
Add ng-change="change(i, $index) to your item and it should work
HTML
<div ng-controller='ctrl'>
<ol>
<li ng-repeat='i in list track by $index'>
<input type='text' ng-model='i' ng-change="change(i, $index)"></input>
<button ng-click='deleteItem($index)'>Delete</button>
</li>
</ol>
<button ng-click='addItem()'>Add</button>
<div>ITEM: {{list | json}}</div>
</div>
Javascript
angular.module("app", []).controller("ctrl", function ($scope) {
$scope.list = ["one","two"];
$scope.addItem = function ()
{
$scope.list.push("");
};
$scope.deleteItem = function (idx)
{
var item_to_delete = $scope.list[idx];
$scope.list.splice(idx, 1);
};
$scope.change = function (item, idx)
{
$scope.list[idx] = item;
};
});
See fixed Demo in DEMO
Yes, pushing more than one empty string will result in ng-repeat complaining.
In addition, you can also try:
if ($scope.aliasList.indexOf(VALUE_TO_ADD) === -1) {
...
}

Resources