I'm making my first steps with angularJS. In the "code school" video (http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/2/video/1) there is a sample code that makes tabs in angular:
HTML
<section class="tab" ng-controller="TabController as tab">
<ul class="nav nav-pills">
<li ng-class="{active:tab.isSet(1)}">
<a href ng-click="tab.setTab(1)">Description</a></li>
<li ng-class="{active:tab.isSet(2)}">
<a href ng-click="tab.setTab(2)">Specs</a></li>
<li ng-class="{active:tab.isSet(3)}">
<a href ng-click="tab.setTab(3)">Reviews</a></li>
</ul>
<div ng-show="tab.isSet(1)">
<h4>Description</h4>
<blockquote>{{product.description}}</blockquote>
</div>
<div ng-show="tab.isSet(2)">
<h4>Specs</h4>
<blockquote>Shine: {{product.shine}}</blockquote>
</div>
<div ng-show="tab.isSet(3)">
<h4>Reviews</h4>
<blockquote></blockquote>
</div>
</section>
JavaScript:
app.controller('TabController', function(){
this.tab = 1;
this.setTab = function(newValue){
this.tab = newValue;
};
this.isSet = function(tabName){
return this.tab === tabName;
};
});
I know that twitter bootstrap has its own JavaScript for managing dynamic tabs (http://getbootstrap.com/javascript/#tabs).
My question is: is angularjs using bootstrap javascript here? I guess not. And if not (while this means angular is using only bootstrap's CSS), then why is angular reinventing the wheel = implementing new code that does the same thing as bootstrap's javascript code? I mean, why writing different code that does the same stuff, why not to use existing code?
Maybe it's just a matter of this tutorial - but is there a way to make angular use native bootstrap's javascript?
bootstrap provides one-way binding data. Howerver, angularJS supplies two-way binding. I suggest you should take a look at angular-ui. If you want use bootstrap with angularjs, you shoud search keyword "custom directive angularjs".
Reinventing the wheel? They are only using ng-show, which just changes the display style in your element to none.
Angular has no problem to use anyone scripts, you only have to do it the angular way. In this case it is called directives (most cases when you are going to manipulate DOM this is the way).
So for angular directives you can use template or templateUrl, template you give a string, templateUrl you give a file path. In your case I recommend you to place a new html file and write there your tabs content.
so in you tabs.html file
<div class="tab-content">
<div class="tab-pane active" class="home">...</div>
<div class="tab-pane" class="profile">...</div>
<div class="tab-pane" class="messages">...</div>
<div class="tab-pane" class="settings">...</div>
</div>
your directive should look something like this, according to bootstrap docs
myapp.directive('theNameOfMyDirective', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'the path to my html file', you can use only template too and writte your html as string, but in your case I think it is more clean if you do it in a diferent file
link: function (scope,element){
// angular.element() this is similar to $() in Jquery or Jquery()
angular.element(element).find('.profile').on('click', function (e){
e.preventDefault()
$(this).tab('show');
});
angular.element(element).find('.home').on('click', function (e){
e.preventDefault()
$(this).tab('show');
});
//some other tabs
}
}
});
since we restrict our directive to be E (Element), we have to add this html to render our tabs wherever we need them
<theNameOfMyDirective></theNameOfMyDirective>
for more info of custom directives http://tutorials.jenkov.com/angularjs/custom-directives.html
Related
I have an issue when creating dynamic id for bootstrap collapse.
First when I click on any topic which is in ng-repeat it should collapse and give me the question list.
The problem here is when I click on the first topic it collapse but when i click on second topic the first topic collapse is getting the second topic question list data
Html:
<div class="topic-div">
<p class="topic-heading">Topics</p>
</div>
<div class="arrow-down"></div>
<ul>
<li ng-repeat="topics in oJdDetails.topics" class="topic-li" ng-click="fngetQList(topics,$index)">
<p class="topics-p"> {{topics}}</p>
<ul uib-collapse="isCollapsed">
<li ng-repeat="value in aQuestionList">{{value.quesList.quesListName}}</li>
</ul>
</li>
</ul>
Js:
$scope.fngetQList = function(topics, index) {
debugger;
$scope.isCollapsed = true;
$scope.displayQList = true;
$scope.sTopics = topics;
$scope.index = index;
getCandidateInterviewListService.fnGetQList(topics).then(function(response) {
$scope.aQuestionList = response;
console.log($scope.aQuestionList);
});
};
I don't understand how to make it work. Any help would be highly appreciated.
It seems you are using plain bootstrap library which will not work with angularjs.
Try with https://angular-ui.github.io/bootstrap/#/collapse
Try avoid using jQuery with Angular especially for Bootstrap. Angular's UI team has written the Angular wrapper for Bootstrap.
http://angular-ui.github.io/bootstrap/
Use this library instead. You don't need the jQuery for this. http://angular-ui.github.io/bootstrap/#/collapse
This uses the Bootstrap's CSS file and the Javascript part is provided by the Angular so you don't need to use jQuery for it.
Angular in itself is a big enough Javascript library so don't bloat your application by installing jQuery as well.
I'm using this snippet from HERE. It looks ok, but when I click on a tab, instead of changing the content of the body (<a href="#tab2success" data-toggle="tab">), it redirects to localhost:9000/#tab2success, which does not exist. How is it possible to achieve this? I just want that when clicking on the tab, it goes to the specific content in the body. Looks like that href is not the solution here...
<div class="col-md-9">
<div class="panel with-nav-tabs panel-success">
<div class="panel-heading">
<ul class="nav nav-tabs">
<li class="active">Success 1</li>
<li>Success 2</li>
<li>Success 3</li>
</ul>
</div>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade in active" id="tab1success">Success 1</div>
<div class="tab-pane fade" id="tab2success">Success 2</div>
<div class="tab-pane fade" id="tab3success">Success 3</div>
</div>
</div>
PD: I'm using angularJS, so if there is any directive or something that could help, it may be also a valid option.
EDIT: Part of my router implementation. It's just o part of the code, because it's too longo to post it all..
angular
.module('Test', [ //setting a module
'oc.lazyLoad',
'ui.router',
'ui.bootstrap',
'angular-loading-bar'
])
.config(['$stateProvider', '$urlRouterProvider', '$ocLazyLoadProvider', function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
//$ocLazyLoad returns a promise that will be rejected when there is an error but if you set debug to true, //$ocLazyLoad will also log all errors to the console.
debug: false,
events: true,
});
// For any unmatched url, redirect to '/dashboard/home'
$urlRouterProvider.otherwise('/dashboard/rank');
// Now we set up the states
$stateProvider
.state('dashboard', { //parent view
url: '/dashboard',
templateUrl: 'views/dashboard/main.html',
This is the normal behavior for href's.
To solve your issue please refer to this article :
Nav tabs in Angular
Basically, there is a flag that controls the visibility of tabs and a ng-click event that will update the value for the flag.
Once the value of the model (flag) was updated, it will automatically update the view.
This is exactly the expected behaviour of href - if you don't supply a full URL, it'll append it onto your current URL (I'm assuming you are using localhost:9000 while developing). Have a look at this link which explains how to use it right, utilising ng-href directive.
To sum it up, you need to use the "handlebars" format inside the ng-href directive:
<a ng-href="{{/#tab2success}}" data-toggle="tab">
There is no need for any Angular code or overcomplications. You are just missing the bootstrap.js file that operates the tabs.
Make sure you are using the correct classes of the tab markup, import the JS file at the end of the body of your document, and that's it.
You can read more about the Bootstrap tabs, the markup, and the way they work in the official Bootstrap documentation.
I've got this accordion made using materializecss framework:
<ul class="collapsible" data-collapsible="accordion">
<li id="licollapse" ng-repeat="single in packageNames">
<div class="collapsible-header">{{single.name}}</div>
<div class="collapsible-body"><p>{{single.name}}</p></div>
</li>
</ul>
and really i tried in several ways to init this accordion but without success! I tried to write in the html view:
<script>$('.collapsible').collapsible();</script>
and it not worked, i tried create in my controller
$scope.collapsible = function() {
$('.collapsible').collapsible({
accordion : false // A setting that changes the collapsible behavior to expandable instead of the default accordion style
});
};
and call collapsible function in ng-init of the accordion and it not worked. The only one solution i found is call that function in ng-click in the collapsible-header but it's not best way because it works only at the second click.. How can i solve?
When you are forced against your dying wish to call external jQuery frameworks or use jQuery in your Angular Application, then the best approach would be to do so in a directive!!!
The DOM should never be touched in your controller, instead, the state of your data in the controller should update the view.
So in this case we can create a simple directive that will have access to that element and call the function in question:
app.directive('collapsify', [collapsifyFn]);
function collapsifyFn(){
return {
restrict: 'A',
compile: function(element, attrs) {
return {
pre: function preLinkFn(scope, element, attrs) {
//if executed here collapsable only is called on an empty <ul>
},
post: function postLinkFn(scope, element, attrs) {
function linkFn(scope, element, attributes) {
debugger;
$(element).collapsible({accordion: true});
}
}
}
}
}
}
An important thing to note here:
Use case for Pre and Post Link:
So because you are generating your <li>'s via an ng-repeat, you need to ensure that you are calling .collapsible in your link function after ng-repeat has rendered the <li>'s to the DOM.
Because of rendering priority, a simple link: would cause the ng-repeat link: to execute after, making your collapsable function not work properly. The purpose of post: is that it executes the linkFn after its children's linkFn has already executed.
Therefore you are accurately calling .collapsible on a "completely rendered dom".
Now that we have created our superfancy collapsify directive, we can attach it to the dom.
<ul collapsify class="collapsible" data-collapsible="accordion">
<li id="licollapse" ng-repeat="single in packageNames">
<div class="collapsible-header">{{single.name}}</div>
<div class="collapsible-body"><p>{{single.name}}</p></div>
</li>
</ul>
Example From Codepen:
<p data-height="268" data-theme-id="0" data-slug-hash="meoWEK" data-default-tab="result" data-user="TheLarkInn" class='codepen'>See the Pen <a href='http://codepen.io/TheLarkInn/pen/meoWEK/'>Using Collapsible MaterializeCSS</a> by Sean Larkin (<a href='http://codepen.io/TheLarkInn'>#TheLarkInn</a>) on <a href='http://codepen.io'>CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
You should wait with calling your materialize code like this:
$(document).ready(function(){
$('.collapsible').collapsible({
accordion : false
});
});
Do it in this way
<ul class="collapsible" data-collapsible="accordion">
<li id="licollapse" ng-repeat="single in packageNames">
<div class="collapsible-header">{{single.name}}</div>
<div class="collapsible-body"><p>{{single.name}}</p></div>
</li>
<script>
//Instert this just after closing </li>
$(document).ready(function() {
$('.collapsible').collapsible({
accordion: true
});
});
</script>
</ul>
It will work for sure :)
So I am using ng-repeat to repeat some divs which show images out of my JSON file. What I want to do is that when I click on that image (whether its desktop or mobile) the image will scale. Now my problem is that when I want to create a click event on my image tag (which is inside that div that holds the ng-repeat), he doesn't do anything. He cant see the click.
I red something on the internet about issues with jquery and angular, but for me as a beginner its hard to understand what I have to do to make it work how I pleased. I just want to be able to put a jquery function on a image tag inside the ng-repeated divs, so I can manipulate the css from there.
I have a piece of the code posted below here, maybe I have to add something to my controller? I dont know, I am clueless at the moment. :-)
<section class="words">
<div class="colored-sidebar"></div>
<!-- content -->
<div class="previous-button"></div>
<div class="word-container" ng-controller="imageController as imageCtrl">
<h1><span>noun</span>words</h1>
<div class="category-body">
<p><span>noun</span>travel</p><hr>
<div class="category-section" ng-repeat="icon in imageCtrl.imageList.travel">
<!-- <div class="category-image" ng-include="icon.src"></div> -->
<div class="category-image">
<img src="{{icon.src}}" />
</div>
</div>
</div>
</section>
The angular file
(function() {
app.controller('imageController', function(){
this.imageList = imageJson;
});
var imageJson = {
//ALOT OF JSON DATA HERE//
};
})();
I hope this piece of code would be enough to help me :-)
Any tips are welcome, I love to learn this language better and also understand it better.
Thanks!
jQuery is not suitable here, because by the time you run your jQuery code inside jQuery.ready(), the elements in "category-image" class are not created yet.
For solution of your problem you can use two methods:
1) Use the "ng-click", as proposed before. You can also pass "$index" to function inside ng-click. This way you will know index of icon in imageList.travel that was clicked. But this way you will have no information about dom element.
2) Create a directive. The main difference between directives and controllers is that directive have information about dom object. You can treat element as typical jQuery object
JS:
app.directive('imageClick', [function () {
return {
link: function (scope, element, attr) {
element.on("click", function(e){
//do some stuff here
})
}
}
}]);
HTML
<section class="words">
<div class="colored-sidebar"></div>
<!-- content -->
<div class="previous-button"></div>
<div class="word-container" ng-controller="imageController as imageCtrl">
<h1><span>noun</span>words</h1>
<div class="category-body">
<p><span>noun</span>travel</p><hr>
<div class="category-section" ng-repeat="icon in imageCtrl.imageList.travel">
<!-- <div class="category-image" ng-include="icon.src"></div> -->
<div class="category-image">
<img image-click src="{{icon.src}}" />
</div>
</div>
</div>
</section>
I have an angular template which looks like this...
<div ng-repeat="message in data.messages" ng-class="message.type">
<div class="info">
<div class="type"></div>
<div class="from">From Avatar</div>
<div class="createdBy">Created By Avatar</div>
<div class="arrowTo">
<div class="arrow"></div>
<div class="to">To Avatar</div>
</div>
<div class="date">
<div class="day">25</div>
<div class="month">Dec</div>
</div>
</div>
<div class="main">
<div class="content">
<div class="heading2">{{message.title}}</div>
<div ng-bind-html="message.content"></div>
</div>
</div>
<br />
<hr />
<br />
</div>
I have set up a JSfiddle to show the data being bound.
What I need to do is make the "from", "to" and "arrowTo" divs show conditionally, depending on the content of the data.
The log is is this...
If there is a "from" object in the data then show the "from" div and bind the data but don't show the "createdBy" div .
If there is no "from" object but there is a "createdBy" object then show the "createdBy" div and bind the data.
If there is a "to" object in the data then show the "arrowTo" div and bind it's data.
Or in plain English, if there is a from address, show it, otherwise show who created the record instead and if there is a to address then show that too.
I have looked into using ng-switch but I think I'd have to add extra markup which would leave an empty div if there was no data. Plus I'd need to nest switch directives and I'm not sure if that would work.
Any ideas?
UPDATE:
If I were to write my own directive (If I knew how!) then here is some pseudo code to show how I would want to use it...
<div ng-if="showFrom()">
From Template Goes Here
</div>
<div ng-if="showCreatedBy()">
CreatedBy Template Goes Here
</div>
<div ng-if="showTo()">
To Template Goes Here
</div>
Each of these would disappear if the function/expression evaluated to false.
Angular 1.1.5 introduced the ng-if directive. That's the best solution for this particular problem. If you are using an older version of Angular, consider using angular-ui's ui-if directive.
If you arrived here looking for answers to the general question of "conditional logic in templates" also consider:
1.1.5 also introduced a ternary operator
ng-switch can be used to conditionally add/remove elements from the DOM
see also How do I conditionally apply CSS styles in AngularJS?
Original answer:
Here is a not-so-great "ng-if" directive:
myApp.directive('ngIf', function() {
return {
link: function(scope, element, attrs) {
if(scope.$eval(attrs.ngIf)) {
// remove '<div ng-if...></div>'
element.replaceWith(element.children())
} else {
element.replaceWith(' ')
}
}
}
});
that allows for this HTML syntax:
<div ng-repeat="message in data.messages" ng-class="message.type">
<hr>
<div ng-if="showFrom(message)">
<div>From: {{message.from.name}}</div>
</div>
<div ng-if="showCreatedBy(message)">
<div>Created by: {{message.createdBy.name}}</div>
</div>
<div ng-if="showTo(message)">
<div>To: {{message.to.name}}</div>
</div>
</div>
Fiddle.
replaceWith() is used to remove unneeded content from the DOM.
Also, as I mentioned on Google+, ng-style can probably be used to conditionally load background images, should you want to use ng-show instead of a custom directive. (For the benefit of other readers, Jon stated on Google+: "both methods use ng-show which I'm trying to avoid because it uses display:none and leaves extra markup in the DOM. This is a particular problem in this scenario because the hidden element will have a background image which will still be loaded in most browsers."). See also How do I conditionally apply CSS styles in AngularJS?
The angular-ui ui-if directive watches for changes to the if condition/expression. Mine doesn't. So, while my simple implementation will update the view correctly if the model changes such that it only affects the template output, it won't update the view correctly if the condition/expression answer changes.
E.g., if the value of a from.name changes in the model, the view will update. But if you delete $scope.data.messages[0].from, the from name will be removed from the view, but the template will not be removed from the view because the if-condition/expression is not being watched.
You could use the ngSwitch directive:
<div ng-switch on="selection" >
<div ng-switch-when="settings">Settings Div</div>
<span ng-switch-when="home">Home Span</span>
<span ng-switch-default>default</span>
</div>
If you don't want the DOM to be loaded with empty divs, you need to create your custom directive using $http to load the (sub)templates and $compile to inject it in the DOM when a certain condition has reached.
This is just an (untested) example. It can and should be optimized:
HTML:
<conditional-template ng-model="element" template-url1="path/to/partial1" template-url2="path/to/partial2"></div>
Directive:
app.directive('conditionalTemplate', function($http, $compile) {
return {
restrict: 'E',
require: '^ngModel',
link: function(sope, element, attrs, ctrl) {
// get template with $http
// check model via ctrl.$viewValue
// compile with $compile
// replace element with element.replaceWith()
}
};
});
You can use ng-show on every div element in the loop. Is this what you've wanted: http://jsfiddle.net/pGwRu/2/ ?
<div class="from" ng-show="message.from">From: {{message.from.name}}</div>