Collection-Repeat and $ionicModal Strange Behavior - javascript

I am making a directory app that comprises roughly 200 list items (employees). The app worked as intended using ng-repeat, however, it was sluggish to load. I switched to Collection-Repeat to take advantage of the speed boost but I am getting bizarre behaviors that I can't figure out.
The list items are rendering correctly, alphabetically with the category titles added successfully. The problem is, each list item has a ng-click attribute that opens an $ionicModal. The modal for each item opens, but the loaded data is incorrect.
When the modal opens, it starts at the bottom of the page - I can see the contents for half a second before it animates to the middle of the screen. To start, the loaded data is correct. As it animates, it switches to another employees data. I can't seem to figure out why. I'm new to angular/ionic so any pointers would be great. Thanks!
EDIT - Out of curiousity, I added a second ng-controller="ModalCtrl" ng-click="openModal();" to each element as a button. Clicking on the element does the usual - opens the modal with the wrong employee. Clicking on the newly created button however creates TWO modals (stacked on eachother) BOTH with the correct employee. Removing either instance to the ng-controller or ng-click puts me back at square one with only one modal of incorrect data. Why is this? Why does adding a second ng-click correct the problem (despite having two modals)?
EDIT - Here is a link to a codepen sample (dumbed down, but proves my issue: http://codepen.io/anon/pen/zijFv?editors=101
My HTML looks like this:
<div class="list">
<a class="item my-item"
collection-repeat="row in contacts"
collection-item-height="getItemHeight(row)"
collection-item-width="'100%'"
ng-class="{'item-divider': row.isLetter}">
<!-- ADDED BUTTON SEE EDIT COMMENT ABOVE -->
<button ng-if="!row.isLetter" ng-controller="ModalCtrl" ng-click="openModal();">Click</button>
<img ng-controller="ModalCtrl" ng-click="modal.show()" ng-if="!row.isLetter" ng-src="data:image/jpeg;base64,{{row.image}}">
<h2>{{row.title || (row.firstname+' '+row.lastname)}}</h2>
<p ng-if="!row.isLetter"><em>{{row.jobtitle}}</em></p>
</a>
</div>
My Modal HTML is this:
<header class="bar bar-header bar-lsi">
<h1 class="title">Contact Information</h1>
<div class="button button-clear" ng-click="closeModal()">
<span class="icon ion-close"></span>
</div>
</header>
<ion-content has-header="true" style="margin-top: 0px !important;">
<div class="list card" style="border-radius: 0px !important;">
<div class="item item-avatar item-text-wrap">
<img ng-src="data:image/jpeg;base64,{{row.image}}">
<h2>{{row.firstname}} {{row.lastname}}</h2>
<p>{{row.jobtitle}}</p>
</div>
<a href="tel:{{row.phone}}" class="item item-icon-left">
<i class="icon ion-iphone"></i>
{{row.phone}}
</a>
<a href="mailto:{{row.email}}" class="item item-icon-left">
<i class="icon ion-email"></i>
{{row.email}}
</a>
</div>
</ion-content>
And then I have my basic controller:
.controller('ModalCtrl', function($scope, $ionicModal) {
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.openModal = function() {
$scope.modal.show();
};
$scope.closeModal = function() {
$scope.modal.hide();
};
$scope.$on('$destroy', function() {
$scope.modal.remove();
});
})

I think the problem is that you aren't passing to the modal template any value. It's getting residual values. I see too that you are using too much ng-controller and ng-click in items list and what is inside it. I mean, if you use ng-click for A.item, you don't need to use ng-click for the image inside it.
Let's see some code:
<a class="item my-item"
collection-repeat="row in contacts"
collection-item-height="getItemHeight(row)"
collection-item-width="'100%'"
ng-class="{'item-divider': row.isLetter}"
ng-controller="ModalCtrl" ng-click="openModal(row);">
<img ng-if="!row.isLetter" ng-src="http://placehold.it/65x65">
<h2>{{row.title || (row.firstname+' '+row.lastname)}}</h2>
<p ng-if="!row.isLetter"><em>{{row.jobtitle}}</em></p>
</a>
As you can see, I've removed all ng-click and ng-controller inside A tag, and I've left only what is attributes of A tag. You can notice too that I pass the object row to the openmModal() function.
In controller, I've made next changes:
$scope.openModal = function(item) {
$scope.modal.row = item;
$scope.modal.show();
};
And in the modal template I've used modal.row as variable with the data from the item list touched. So in template I use it like this:
<div class="item item-avatar item-text-wrap">
<img ng-src="http://placehold.it/65x65">
<h2>{{modal.row.firstname}} {{modal.row.lastname}}</h2>
<p>{{modal.row.jobtitle}}</p>
</div>
<a href="tel:{{modal.row.phone}}" class="item item-icon-left">
<i class="icon ion-iphone"></i>
{{modal.row.phone}}
</a>
<a href="mailto:{{modal.row.email}}" class="item item-icon-left">
<i class="icon ion-email"></i>
{{modal.row.email}}
</a>
I've test it in your codepen and it works. Try it and tell me if it works for you.

Related

AngularJS : Adding animation when div tag is updated

I am stuck in this problem where I have a div tag which updates and shows a list of images. I want to add animation when the value in the div tag is updated in the transition from one set of images to another.
Here as you can see in the bottom there are a set of images for girl's hair. And when the user goes to other tab, a different set of images comes. I want animation in that transition.
The AngularJS part for the transition is as follows :
<div ng-swipe-left="avDesignController.onSwipeLeftAction()" ng-swipe-right="avDesignController.onSwipeRightAction()">
<!-- CUSTOMIZABLE TAB BAR -->
<div class="tab-bar-container" >
<div class="scrollmenutab">
<!-- CUSTOMIZABLE MENU -->
<div ng-repeat="customizable in customizables"
ng-click="avDesignController.onCustomizableClicked($event)"
style="display: inline;">
<a ng-if="customizable.allowed == 1">
<div ng-class="{selected: $index==currentIndex}">
<div ng-if="customizable.name == 'Hair'">
<img class="scrollmenutab-icon"
id="{{customizable.name}}-{{$index}}"
src="/app/app_resources/icons/{{genderImage}}Hair.png">
</div>
<div ng-if="customizable.name != 'Hair'">
<img class="scrollmenutab-icon"
id="{{customizable.name}}-{{$index}}"
src="/app/app_resources/icons/{{customizable.name}}.png">
</div>
</div>
</a>
</div> <!-- MENU : END -->
</div>
</div>
<!-- CUSTOMIZABLES -->
<div class="avdesign-item-container" id="avdesign-item-container">
<div id="four-columns" class="grid-container" >
<!-- LOAD CUSTOMIZABLES IF NOT LAST ITEM IN TAB -->
<ul ng-if="currentIndex < (customizables.length - 1)"
class="rig columns-4">
<li ng-repeat="customizableItem in currentCustomizable.customizable_item">
<img class="tab-icon"
src="/app/app_resources/resources/av/{{avatarInfo.name}}/as/{{customizableItem.as}}"
id="customizable-{{$index}}"
ng-click="avDesignController.onCustomizedItemClicked($event)"
ng-class="{highlight: customizableItem.id==currentID}">
</li>
</ul>
<!-- LOAD OUTFITS (FROM avatarOutfit) IF LAST ITEM IN TAB -->
<ul ng-if="currentIndex == (customizables.length - 1)"
class="rig columns-outfit">
<div ng-repeat="brand in outfitBrand" ng-style="{'margin-bottom':'1vh'}">
<div class="brand-icon" >
<img src="/app/app_resources/icons/{{brand.bg_image}}">
</div>
<li ng-repeat="outfit in brand.outfitList">
<img class="outfit-icon"
src="/app/app_resources/resources/av/{{avatarInfo.name}}/as/{{outfit.as}}"
id="outfit-{{$index}}"
ng-click="avDesignController.onOutfitItemClicked($event,$parent.$index)"
ng-class="{highlightOutfit: $index==avatar.outfit_index && $parent.$index==indexParent}">
</li>
</div>
</ul>
</div>
</div>
</div>
Where the functions being called in the JS part is updating accordingly the images.
So Question being how to add transition animation for the same element when it is updated because we are never leaving or entering that element tag
Your question is answered here
Html
<div ng-controller="c">
<div id={{my_id}} width={{widthOfOutsideWrapper}} height={{heightOfOutsideWrapper}}>
<img ng-src="{{src}}" imageonload />
</div>
</div>
and in controller
var app = angular.module('app', []);
app.directive('imageonload', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('load', function() {
alert('image is loaded');
scope.widthOfOutsideWrapper= this.width;
scope.heightOfOutsideWrapper= this.height;
scope.my_id = "testing";
});
}
};
});
app.controller('c', function($scope) {
$scope.src ="https://www.google.com.ua/images/srpr/logo4w.png";
});
and http://jsfiddle.net/2CsfZ/855/ working example is available here
The above answer is very helpful and might be of a great help for many.
But the answer to my question is you simply can't. ngAnimate's ngEnter and ngLeave will not able to recognize a change unless the element is changed and not what is inside the element.
So either some hack needs to be applied like using ngRepeat with different id's for different updation that happens to the element or something else needs to be done.

Angularjs nested controller called just one time

I'm new in Angularjs and I have an app, with some "projects", which have a local menu displayed on some pages. Index.html contains the main navbar with footer :
<body ng-app="ysi-app" ng-controller="MainController">
<div class="page-content">
<div class="row">
<div class="col-md-2">
<div class="sidebar content-box" style="display: block;">
<ul class="nav">
<!-- Main menu -->
<li ng-if="isAuthenticated()">{{name}}</li>
<li class="current">Dashboard</li>
<li>Projects</li>
</ul>
</div>
<div ng-if="isAuthenticated() && displayProjectMenu == true" ng-include="'views/localMenu.html'" ng-controller="LocalMenuController">
</div>
</div>
<div ng-view></div>
</div>
So I have a nested controller LocalMenuController for the local menu and a main controller. The project controller sets the datas :
angular.module('ProjectCtrl',[]).controller('ProjectController',function($scope,$location, ProjectService,$route, AuthenticationService, $rootScope){
$scope.setProjectDatas = function(projectName, projectId){
ProjectService.setName(projectName);
$rootScope.projectId = projectId;
};});
I set the id of one project to the $rootScope for testing (I have a Service which will do that better) and get it in the LocalMenuController :
angular.module('LocalMenuCtrl',[]).controller('LocalMenuController', function($scope, ProjectService, $rootScope) {
$scope.projectId = '';
$scope.projectId = $rootScope.projectId;
});
I display projects in a table and when I clicked on one of it, the function setProjectDatas(name,id) is called. The problem is when I clicked on one project, the id of the project is correct and set but when I go previous and clicked on another project, the id is the old id of the project previously clicked. The datas are not updating. I googled my problem but found nothing on it.
I think the LocalMenuController is called only one time but not after.
What am I doing wrong ?
Thank you
UPDATE
I've created a Directive which displays the template but it's still not updating the partial view localMenu.
LocalMenu Directive :
angular.module('LocalMenuCtrl',[]).controller('LocalMenuController', function($scope, ProjectService, $rootScope) {
console.log('-> LocalMenu controller');
})
.directive('localMenu', function($rootScope){
return {
templateUrl: '/YSI-Dev/public/views/partials/localMenu.html',
link: function(scope){
scope.projectId = $rootScope.projectId;
}
};
});
A part of index.html
<div ng-if="isAuthenticated() && displayProjectMenu == true" ng-controller="LocalMenuController">
<div local-menu></div>
</div>
Partial view localMenu :
<div class="sidebar content-box" style="display: block;">
<ul class="nav">
<li><i class="glyphicon glyphicon-list-alt"></i> Backlog</li>
<li><i class="glyphicon glyphicon-user"></i> My team </li>
</ul>
</div>
I'm trying to get the projectId from the $rootScope and inject it in the <a href="#/project/{{projectId}}" but I have some troubles. What's the way to do that ?
First of all, try using directives instead of ng-controller. You can encapsulate your code and template into a unit. You can also try creating a component. Pass some data to the directive/component and Angular will take care of updating the template and running whatever needs to run within the directive/component. (Given that you used two-way data-bindings)
From the code above, I cannot see what would trigger LocalMenuController to run again.

Problems with Angular button

I'm creating my first Angular app and ran into a couple things that I just can't figure out. Whenever I include this:
<button ng-hide="results.length === projects.length" ng-click="limit = limit +3; gotoBottom()">Show More</button>
Inside of my template the app refuses to load but if I paste it anywhere outside of the template it works fine. I'd like to keep the button inside the template if at all possible so what on earth am I doing wrong?
Also, I'd like that button to also scroll to the #footer div and the ng-click doesn't seem to run this bit code:
$scope.gotoBottom = function() {
$location.hash('footer');
$anchorScroll();
};
I've created a Plunker of my code that can be found here:
https://plnkr.co/edit/MP4Pp4WLcn5EFb3pTEXx
By "template" if you are talking about projects template. Here is what you need to do.
Explanation:
The projects template need to have only one root element, so I added a div to wrap your project listing and show more button.
<div>
<div class="cards" ng-init="limit = 3">
<div class="card" ng-repeat="project in projects | limitTo: limit as results">
<div class="card-image">
<img src="{{project.img}}" alt="{{project.name}}" />
</div>
<div class="card-copy">
<h2>{{project.name}}</h2>
<p>{{project.desc}}</p>
<p><i class="fa fa-location-arrow"></i></p>
</div>
</div>
</div>
<button ng-hide="results.length === projects.length" ng-click="limit = limit +3; gotoBottom()">Show More</button>
<div id="footer" name="footer"></div>
</div>
For auto scroll: inject $timeout service
Explanation:
You did not had any div named footer so I added one just below the show more button and added a 100ms timeout, so that after your 3 projects load, it will scroll to the footer div. $timeout is very necessary because need to first render your projects and then scroll.
$scope.gotoBottom = function() {
$timeout(function() {
$location.hash('footer');
$anchorScroll();
}, 100);
};
Working Plunker: https://plnkr.co/edit/U3DDH57nh0Mqlpp2Txi4?p=preview
Hope this helps!
change the below code in projects.js
angular.module('portfolioApp')
.directive('projects', function() {
return {
templateUrl: 'projects.html',
controller: 'mainCtrl',
replace: true // remove directive tags
};
});
to
replace: false
it should do the trick. Plunker Link here

ng-click not firing $scope.doSomething(), onclick for same function works

The controller I'm using is:
angular.module('app.PostView', [])
.controller('PostViewCtrl', function($scope, $http, Constants) {
$scope.posts = [];
$scope.doSomething = function(){
console.log("in doSomething");
}
$http.get(Constants.POST_URL)
.then(function (response){
console.log(response);
var post = new PostFactory(response.data);
$scope.posts.push(post);
});
})
The view for the controller is
<div ng-repeat="post in posts" >
<div class="home-container">
<div id="details-container">
<!-- using single item Array instead of single iftem to fix linkify bug -->
<div ng-bind="post.desc"></div>
<span class="item-note single-post-details">
<!-- <div class="time-text">{{post.id}}</div> -->
<div class="title" >{{post.title}}</div>
</span>
<a ng-click="doSomething()" href="#" >{{post.name}}</a>
</div>
</div>
</div>
If I replace ng-click with onclick then doSomething is triggered. Currently it is not. The same Controller and html code in other controller/views does work.
When you clicking on the anchor, it change the routing as http://url/#, that listen by the $routeProvider & ng-view loads the default view and template as you are seeing on your side.
Basically you need to remove href="#" from your anchor link or just put href="" blank in that anchor. That would fix your problem.
<a ng-click="doSomething()" href="" >{{post.name}}</a>
Remove href completely
<a ng-click="doSomething()"
style='cursor:pointer' >{{post.name}}</a>

Knockout Template in foreach access specific template item

I am populating a list of survey questions. On click of a survey question, a modal with graphs of the results pop up. To create my list I am using a knockout template as I need the afterRender function.
Here's my markup:
<div id="priorityMenuW" class="priorityMenuW shadow">
<div class="menuHeader">Select a Survey Question:</div>
<div id="priorityMenu" data-bind="foreach:questionTypes">
<div class="menucategory menuItem" data-toggle="tooltip" data-bind="text:CategoryName, attr:{title:CategoryName}"></div>
<div class="menuitem" data-toggle="tooltip" data-bind="foreach:$root.questions">
<!-- ko if: CategoryName == $parent.CategoryName-->
<div data-bind='template: { name: "question-template",
data:$root.questions,
afterRender: $root.storeQuestionIdOrder }'>
</div>
<!-- /ko -->
</div>
</div>
</div>
And my template:
<code><script type="text/html" id="question-template">
<div class="menuItem" data-toggle="tooltip" data-bind="html:'• '+ $parent.QuestionText, attr:{title:$parent.QuestionText}, css: {'itemSelected' : $root.isPriorityActive($data)}, click: function($data,event){$root.questionChoice($data,event)}"></div></script></code>
My problem is that by sending $data to the function questionChoice, I am receiving an array of all of the templated objects. How can I access the specific object clicked on? I was thinking maybe $data[$index], but that doesn't work.
if you want to use $data[$index], remember that $index is an observable and needs to be evaluated:
$data[$index()]

Categories