Individual toggles inside an ng-repeat - javascript

I have some content coming in which I am running through a repeat, each one of these content items has to have a toggle open/close functionality to it. I figured my best bet in marking them individually is using something like the $index of the item in the repeat. I am just using a (bootstrap) class to show/hide like so :
<a ng-click="{{$index}}inner = !{{$index}}inner" >{{item.node.id}}</a>
<div class="nested " ng-class="{'collapse' : !{{$index}}inner">
So - a click toggle on the a above. This does not work because of the syntax, but what I'm wondering here is if there is a nice elegant solution to this problem. This way I tried first seems a little messy. Thanks!

The ng-repeat directive created a scope for each of the elements. Therefore you could just use the ng-click to toggle a property on that scope.
Also the syntax of your ng-class is not totally correct.
For working example see http://jsbin.com/yuyucojeco/edit?html,js,output

I like adding properties to the actual objects themselves. Then, within your ng-repeat, you can just reference the individual object (instead of trying to re-index into the array) and apply all logic as you need.
Maybe something like this:
<div ng-repeat="item in array">
<a ng-click="item.collapsed = !item.collapsed" >{{item.node.id}}</a>
<div class="nested " ng-class="{'collapse' : item.collapsed"></div>
</div>

I have implemented smt similar inside an ng-repeat using bootstrap as well like so:
here is the button:
<a data-toggle="collapse" data-target="#{{item.id}}" class="accordion-toggle btn btn-xs btn-success"></a>
then the collapsible tab:
<div class="accordion-body collapse" id="{{item.id}}"> <!-- accordion starts -->
<!-- some content here -->
</div> <!-- accordion ends -->

Related

Angular Dragula and ng-repeat $index

I have an ng-repeat of rows. I set them up so you can reorder them with drag-and-drop using Angular Dragula. This works fine, but the ng-repeat $index remains the initial value for each item after dragging. See screen captures below before and after a drag of the "Recipient" row.
Is there a way to update the index? I also want to re-order the menu with javascript, with that button in each row, but for that to work I need to get the current $index (and decrement/increment it) and that won't work if the index is incorrect like this.
Before Drag
After Drag
For what it's worth, this is the answer:
<div class="table-cards-body" dragula="'first-bag'" dragula-model="home.quickReceiveFields">
<div class="table-cards-row drag-item" ng-repeat="field in home.quickReceiveFields track by $index">
<div class="one">{{field.name}}</div>
<div class="two">{{field.type}}</div>
<div class="three">{{field.default}} {{$index}}</div>
<div class="four"><input type="checkbox" ng-model="field.display"></div>
<div class="five"><input type="checkbox" ng-model="field.retain"></div>
<button class="btn btn-default btn-xs" ng-click="home.moveItemUp($index)">^</button>
</div>
</div>
Despite what the docs say on the angular-dragula site, you need to put the dragula-model attribute on the container into which you put the repeating items. Not the ng-repeat itself.
Try to add in ng-repeat track by $index.

Trying to set ng-class of AngularJS accordion-heading does nothing

I have an accordion where some of the entries need to have a heading that emphasizes that the data in that group shows a problem that needs attention. I tried using a "accordion-heading" with a "ng-class" for "has-error" (from bootstrap) that is conditional on the method that determines whether there is something that needs attention. I've tried several variations of this, and the "class" attribute never gets rendered in the HTML.
This is an excerpt from my HTML:
<accordion close-others="false">
<div ng-repeat="(name, dataSource) in dataSourceMap">
<accordion-group>
<accordion-heading>
<span ng-class="{'has-error': anyFailuresInList(dataSource)}">
{{name}}
</span>
</accordion-heading>
{{anyFailuresInList(dataSource)}}
</accordion-group>
</div>
</accordion>
The example in the documentation at enter link description here indicates that this should be possible, even though the example is a little broken (it appears to put the class on an empty "i" element).
accordion-group doesn't support ng-class well.
Please use class instead.
If you want to set the style conditionally, you can use the ? operator. For instance:
<uib-accordion-group class="{{isSpecial ? 'special-style':''}}">
This can be done by applying a css class to the group instead of the header directly
<div accordion-group is-open="node.active" is-disabled="node.active" ng-class="{'panel-active': node.active}" ng-repeat="(key,node) in nav.nodes">
<div accordion-heading >{{node.title}}</div>
</div>
Then in your css change the back for the first DIV
.panel-active>div {
background-color: #YourActiveColor;
border-color: #YourActiveColor;
}

angular-ui > ui-utils > ui-scroll does not work (v. 0.1.0)

I am using this: http://angular-ui.github.io/ui-utils/ and to be more specific this:https://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md
however it does not seem to work. Here is an example:
<div ng-scroll-viewport style="height:240px;" class="chat-messages">
<div class="chat-message" ng-scroll="chat in chats">
<div ng-class="$index % 2 == 0? 'sender pull-right' : 'sender pull-left'">
<div class="icon">
<img src="{{chat.img}}" class="img-circle" alt="">
</div>
<div class="time">
{{chat.time}}
</div>
</div>
<div ng-class="$index % 2 == 0? 'chat-message-body on-left' : 'chat-message-body'">
<span class="arrow"></span>
<div class="sender">{{chat.name}}</div>
<div class="text">
{{chat.msg}}
</div>
</div>
</div>
</div>
But All I get in HTML is this :
<div class="chat">
<div class="chat-messages" style="height:240px;" ng-scroll-viewport="">
<!--
ngScroll: chat in chats
-->
</div>
If I replace ng-scroll with ng-repeat, it works perfectly. But chats need scroll bars, so... How can I get one? :)
The documentation of ngScroll directive had also tricked me into simply replacing ng-repeat by ng-scroll. Unfortunately, it turned out not as simple as that, see also the small, working example at http://plnkr.co/edit/fWhe4vBL6pevcuLutGC4 .
Note that
"datasource" (or whatever object you want to iterate over for the contents of the scroll list) must implement a method "get(index,count,success)" that calls success(results), see hXXps://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md#data-source
The array must have exactly count elements. Otherwise, no scroll window/bar will ever show, which can be very irritating!
Although UI.Utils says it has no external dependencies, ui.scroll has actually a dependency on either ui.scroll.jqlite, or jQuery. So make sure to list both ui.scroll and ui.scroll.jqlite in your module definition which contains the controller with datasource object (and load their .js files, or load ui-utils.js which contains both), see https://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md#dependencies
Be careful when your server is sending some Content Security Policies (CSP). Maybe turn them off while trying to get ng-scroll to work first, then re-apply CSP and tune the policies accordingly for ui.scroll to work.
One way of getting a scroll is to use CSS, set overflow-y to scroll and you will get scroll bar.
If you need to scroll to the bottom, play with anchorScroll
http://docs.angularjs.org/api/ng.$anchorScroll.

Difficulty with selective show/hide based on CSS class

I'm working on a js script which will show / hide multiple divs based on css class, seemingly pretty simple. I set out to find an example of this and found something close in the article linked below. I used the code in the following link as a starting point.
Show/hide multiple divs using JavaScript
In my modified code (shown below) I am able to hide all (which is errant) and show all (which works correctly. I'm not sure why its not targeting the CSS class "red, green or blue" correctly. If I hard one of the class names in the script it works as expected, so I'm fairly certain I'm having an issue in the way I'm referencing the css targets themselves.
I am able to hide all and show all, yet I'm having difficulty showing only the selected class.
Here is the jsFiddle I'm working with: http://jsfiddle.net/juicycreative/WHpXz/4/
My code is below.
JavaScript
$('.categories li a').click(function () {
$('.marker').hide();
$((this).attr('target')).show();
});
$('#cat-show').click(function () {
$('.marker').show();
});
HTML
<ul class="categories">
<li id="cat-show" class="cat-col1" target="all" >All</li>
<li id="cat-models" class="cat-col1" target="red" >Model Homes</li>
<li id="cat-schools" class="cat-col1" target="blue">Schools</li>
<li id="cat-hospital" class="cat-col1" target="green" >Hospitals</li>
</ul>
<div id="locator">
<div id="overview-00" class="marker models" title="Maracay Homes<br />at Artesian Ranch"></div>
<!--SCHOOLS-->
<div id="overview-01" class="marker red" title="Perry High School">1</div>
<div id="overview-02" class="marker red" title="Payne Jr. High School">2</div>
<div id="overview-03" class="marker blue" title="Hamilton Prep">3</div>
<div id="overview-04" class="marker blue" title="Ryan Elementary">4</div>
<div id="overview-05" class="marker green" title="Chandler Traditional – Freedom">5</div>
</div>
Thanks in advance for any responses.
$((this).attr('target')).show();
This is syntactically incorrect. It should be $($(this).attr('target'))
However that's no good either because this is the anchor element that does not have the target. Use $(this).closest('li').attr('target') (or add the target to the <a>).
This is also semantically incorrect as that would interpolate to $("red") which would try to look for a <red> element.
$("." + $(this).closest('li').attr('target'))
http://jsfiddle.net/WHpXz/5/
You are almost there. This is the line that needs tweaking: $((this).attr('target')).show();
$(this) actually refers to the current anchor tag that was clicked. Since the anchor tag doesn't have the target attribute, you need to go up to the parent.
From there, you can get the target and add the '.' to the color to use as a selector.
var catToShow = $(this).parent().attr('target');
$('.' + catToShow).show();
I've edited your fiddle. Give it a shot.
http://jsfiddle.net/juicycreative/WHpXz/4/

Can you specify a "data-target" for Bootstrap which refers to a sibling DOM element without using an ID?

I am dynamically adding Collapsable elements to a page. Bootstrap uses the "data-target" attribute to specify which element the collapse toggle applies to.
From the docs:
The data-target attribute accepts a css selector
Is there a way to write a selector which specifies the next sibling of the parent element? All of the examples from the docs seem to use selections by ID.
Specifically the HTML looks like:
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-target="#collapseOne">
Generated Title
</a>
</div>
<div id="collapseOne" class="accordion-body collapse in">
<div class="accordion-inner">
Generated Content... this is big and sometimes needs collapsing
</div>
</div>
</div>
I would like to write something like (pseudo code using jquery syntax illegally):
<a class="accordion-toggle" data-toggle="collapse" data-target="$(this).parent().next()">
But I am starting to suspect this may not be possible with CSS selectors.
Right now as a workaround I am generating a new ID (an incrementing number appended to a string) when I create the element.
Is there a nicer approach using a selector? Should I be using some post-creation javascript to set the data-target attribute? Is generating IDs for dynamic content the standard approach?
While it is true that the selector in a data-target attribute is a jQuery selector, the data-api specification for this plugin provides no means of referencing back to this in the scope of execution (see lines 147-153 in bootstrap-collapse.js for its use).
However, I would like to offer another alternative approach, which is to extend the data-api with your own custom toggle specifier. Let's call it collapse-next.
JS (see update note)
$('body').on('click.collapse-next.data-api', '[data-toggle=collapse-next]', function (e) {
var $target = $(this).parent().next()
$target.data('collapse') ? $target.collapse('toggle') : $target.collapse()
})
HTML
<a class="accordion-toggle" data-toggle="collapse-next">
JSFiddle (updated)
Downside here is that it's a rather tightly coupled approach, since the JS presumes a specific structure to the markup.
Note about IE issues
As #slhck pointed out in his answer, IE9 and under apparently fail on the first click when using an earlier revision of my answer. The cause is actually not an IE issue at all, but rather a Bootstrap one. If one invokes .collapse('toggle') on a target whose Carousel object is uninitialized, the toggle() method will be called twice - once during initialization and then again explicitly after initialization. This is definitely a Bootstrap bug and hopefully will get fixed. The only reason it doesn't appear as a problem in Chrome, FF, IE10, etc, is because they all support CSS transitions, and hence when the second call is made it short-circuits because the first one is still active. The updated workaround above merely avoids the double-call problem by checking for initialization first and handling it differently.
#merv's solution didn't work for me in IE9 and below, since the collapsible state wasn't available unless you clicked at each item once. It did work fine in Firefox and Chrome though. So after two clicks, everything would work.
What I did was set a .collapse-next class to the triggering elements, then force their ul siblings to collapse with toggle set to false:
$(".collapse-next").closest('li').each(function(){
if ($(this).hasClass('active')) {
// pop up active menu items
$(this).children("ul").collapse('show');
} else {
// just make it collapsible but don't expand
$(this).children("ul").collapse({ toggle: false });
}
});
This is for actually toggling the menu state:
$('.collapse-next').click(function(e){
e.preventDefault();
$(this).parent().next().collapse('toggle');
});
It seems that using data- attributes is a somewhat more modern and cleaner approach, but for old browsers working with classes and jQuery seems to do the job as well.
No javascript solution or the solution depends on bootstrap's JS already in use, just exploiting the DOM structure-
See the data-target=""...
A hint to avoid bulky solutions and need no ID or extra JS, trick using markup placement-
<button data-toggle="collapse" data-target=".dropdown-toggle:hover + .more-menu" type="button" class="btn btn-primary dropdown-toggle" >
Show More Menu +
</button>
<div class="collapse more-menu">More menu here...</div>
This CSS selection structure will select the desired DOM .dropdown-toggle:hover + .more-menu and there we can apply our desired CSS.
There are more ways to exploit what we have. :hover or :active or so many other ways.
Yes, you can do it easily! 😎
Just add the following code to your scripts and enjoy: 😃
$('body').on('click','[data-toggle="collapse"][data-mytarget^="$("]',function(){
eval($(this).data('mytarget')).collapse('toggle');
});
Now every where in your code you can set the target of collapse by a fully dynamic jQuery command inside data-mytarget.
Now use it like it:
<a data-toggle="collapse" data-mytarget="$(this).parent().next()">Link</a>
or
<a data-toggle="collapse" data-mytarget="$('#TopMenuBar').closest('ul')">Link</a>
or
<a data-toggle="collapse" data-mytarget="[ EACH IDEAl JQUERY CODE OF YOU STARTED WITH $( ]">Link</a>
Demo:
$('body').on('click','[data-toggle="collapse"][data-mytarget^="$("]',function(){
eval($(this).data('mytarget')).collapse('toggle');
});
a{
padding:10px;
cursor:pointer;
margin:20px;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle btn btn-info" data-toggle="collapse" data-mytarget="$(this).parent().next()">Collapse It</a>
</div>
<div id="collapseOne" class="accordion-body collapse ">
<div class="accordion-inner">
Generated Content... this is big and sometimes needs collapsing
</div>
</div>
</div>
I used data-mytarget instead of data-target. If you use data-target it works too but the jQuery library raise an error like this: Uncaught Error: Syntax error, unrecognized expression: $(this).parent().next().
So I defined my different property with data-mytarget name.
I think the best approach would be to do this iterate all accordion-toggle elements and set their data-target attribute dynamically via jquery and after that place the code of accordion mentioned in bootstrap.
Example :
$(function(){
$("a.accordion-toggle").each(function(index) {
$(this).attr("data-target", "#" + $(this).parent().next().attr("id"));
});
// accoridon code
});
Hope this will help
TYPO3 FCE and Bootstrap Accordion
I am having some trouble with this issue too i am using it in TYPO3 for a customer who wants to be able to add an infinite number of elements to the accordion. So I created a Flexible Content Element and mapped the elements.
The idea with that data-toggle="collapse-next" did not work for me as expected as it did not close the open elements. I created a new javascript-function doing that please find the code here. Hopefully someone finds the stuff useful.
Javascript
$(document).on('click.collapse-next.data-api', '[data-toggle=collapse-next]', function (e) {
var $container = $(this).parents(".accordion");
var $opencontainers = $container.find(".in");
var $target = $(this).parent().next();
$target.data('collapse') ? $target.collapse('toggle') : $target.collapse();
$opencontainers.each(function() {$(this).collapse('toggle')});
})
HTML
<html>
<div class="accordion">
<div class="accordion-section">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse-next">
Collapsible Group Item #1
</a>
</div>
<div class="accordion-body collapse">
<div class="accordion-inner">
Anim pariatur cliche...
</div>
</div>
</div>
</div>
</div>
</html>
Here is an approach that avoids needing unique IDs and avoids a specific html structure.
This encloses each instance of the collapse trigger and target in a "section" of html. I like to use class selectors, especially for multiple instances. In this case, it avoids having to create artificial unique IDs.
Borrowing from #merv's excellent answer, I named the data-toggle collapse-section similar to his collapse-next, and added a data-section attribute.
Instead of parent().next(), this looks up for a closest() section name, then down for the given target name. They can be siblings or any other level.
(I have a naming convention using "id..." as a prefix for class names that are used as jquery selectors, to avoid mixing with styling.)
HTML
<div class="accordion-group idCollapseSection">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse-section"
data-section=".idCollapseSection" data-target=".idCollapseTarget">
Generated Title
</a>
</div>
<div>Any other html, at various depths.
<div class="accordion-body collapse in idCollapseTarget">
<div class="accordion-inner">
Generated Content... this is big and sometimes needs collapsing
</div>
</div>
</div>
</div>
JS
//------------------------------
// Bootstrap Collapse.
//------------------------------
// Extend collapse to contain trigger and target within an enclosing section.
$('body').on('click.collapse-section.data-api', '[data-toggle=collapse-section]', function (e) {
var thisTrigger = $(this);
var sectionSelector = thisTrigger.data("section");
var targetSelector = thisTrigger.data("target");
var target = thisTrigger.closest(sectionSelector).find(targetSelector);
target.data('bs.collapse') ? target.collapse('toggle') : target.collapse();
});

Categories

Resources