InfiniteScroll - AngularJS not working - angularjs

Edit:
Just for checking purposes, I also did a console.log inside the nextPage function, to check if it's being triggered:
$scope.nextPage = function() {
var captureLength = $scope.captures.length;
console.log('TRIGGER');
if($scope.busy) {
return;
}
...
}
};
And it seems I'm getting a infinite loop, but I can't see why.
=================================
I'm trying to implement infinitescroll into a view but for some reason it's only loading the initial 4 images and not triggering the rest.
Here is my code:
CTRL:
/* ----------------------- Variables ----------------------- */
$scope.auth = auth;
$scope.captures = [];
$scope.following = [];
$scope.allData = [];
$scope.busy = true;
var page = 0;
var step = 4;
$scope.nextPage = function() {
var captureLength = $scope.captures.length;
if($scope.busy) {
return;
}
$scope.busy = true;
$scope.captures = $scope.captures.concat($scope.allData.splice(page * step, step));
page++;
$scope.busy = false;
if($scope.captures.length === 0) {
$scope.noMoreData = true;
}
};
/* ----------------------- Process Data ----------------------- */
$q.all({follows: findFollow(), users: getUsers(), captures: getAllCaptures()}).then(function(collections) {
var follows = collections.follows;
var users = collections.users;
var captures = collections.captures;
follows.filter(function(follow) {
return follow.follower_id === auth.profile.user_id;
}).forEach(function(follow) {
users.filter(function(user) {
return user.user_id === follow.followed_id;
}).forEach(function(user) {
$scope.following.push(user);
});
});
follows.filter(function(follow) {
return follow.follower_id === auth.profile.user_id;
}).forEach(function(follow) {
captures.filter(function(capture){
return follow.followed_id === capture.userId;
}).forEach(function(capture){
console.log(capture);
$scope.allData.push(capture);
});
});
$scope.nextPage();
$scope.busy = false;
});
/* ----------------------- Retrieve Services - Data ----------------------- */
function findFollow() {
return userApi.findFollow().then(function(res) {
return res.data;
});
}
function getUsers() {
return userApi.getUsers().then(function(res) {
return res.data.users;
});
}
function getAllCaptures() {
return captureApi.getAllCaptures().then(function(res) {
return res.data;
});
}
Partial:
<div class="col-md-8">
<div class="well main-well">
<h3 class="page-header-h3">Following Dashboard:</h3>
<hr />
<h4 align="center" ng-show="!captures.length">
<strong>The people that you are following, have not posted anything yet.. Yikes!</strong>
<br /><br />
Quickly, go follow more people!</h4>
<div class="row" infinite-scroll="nextPage()" infinite-scroll-disabled="busy || noMoreData" infinite-scroll-distance="0.1">
<ul class="dynamic-grid" angular-grid="captures" ag-id="gallery">
<li data-ng-repeat="capture in captures | orderBy :'created_at':true" class="grid">
<a ui-sref="detail({id: capture._id})">
<img ng-src="{{capture.picture}}" class="grid-img" />
<span class="follow-capture-info">
<span class="follow-capture-name"><span class="glyphicon glyphicon-user"></span>
{{capture.author}}
<span class="following-capture-time">ยท
<span class="glyphicon glyphicon-time"></span>
<span am-time-ago="capture.created_at"></span>
</span>
</span>
</span>
</a>
</li>
</ul>
</div>
<div ng-show="busy">Loading more...</div>
</div>
Anyone know where I went wrong?
Thanks.

Related

Angular filter multiple columns with an array of filters

I am trying to be able to filter multiple columns on multiple values. So far I can filter multiple columns on 1 single value:
myApp.controller('MyCtrl', ['$scope', '$http', function($scope, $http) {
$scope.empList = [];
$http.get('getAllOnline.php')
.success(function(data) {
$scope.empList = data;
});
$scope.column1List = [];
$http.get('getAllSomething.php', {
params: {
wuk: "column1"
}
})
.success(function(data) {
$scope.column1 = data;
});
$scope.column2List = [];
$http.get('getAllSomething.php', {
params: {
wuk: "column2"
}
})
.success(function(data) {
$scope.column2 = data;
});
$scope.column3List = [];
$http.get('getAllSomething.php', {
params: {
wuk: "column3"
}
})
.success(function(data) {
$scope.column3 = data;
});
$scope.setColumn1Value = function(val) {
if ($scope.zelectedColumn1 == val) {
$scope.zelectedColumn1 = undefined;
} else {
$scope.zelectedColumn1 = val;
}
}
$scope.setColumn2Value = function(val) {
if ($scope.zelectedColumn2 == val) {
$scope.zelectedColumn2 = undefined;
} else {
$scope.zelectedColumn2 = val;
}
}
$scope.setColumn3Value = function(val) {
if ($scope.zelectedColumn3 == val) {
$scope.zelectedColumn3 = undefined;
} else {
$scope.zelectedColumn3 = val;
}
}
Then i use this to set my single value filters:
<ul id="Column1" class="collapse">
<li ng-repeat="emp in empList | unique:'Column1'">
<a ng-click="setColumn1Value(emp.Column1);" ng-class="{selected: emp.Column1 === zelectedColumn1}">
<div ng-repeat="someone in Column1List | filter:{Column1_id:emp.Column1}">
{{someone.value}}
</div>
</a>
</li>
</ul>
This works perfect! But now I want to be able to filter on multiple values in 1 column. So I changed my setter functions to:
$scope.zelectedColumn1=[];
$scope.setColumn1Value = function(val) {
var found = jQuery.inArray(val, $scope.zelectedColumn1);
if (found >= 0) {
// Element was found, remove it.
$scope.zelectedColumn1.splice(found, 1);
} else {
// Element was not found, add it.
$scope.zelectedColumn1.push(val);
}
console.log($scope.zelectedColumn1);
}
So now I add or remove indexes to an array instead of storing a single value. This also works, but how do I filter my columns on the contents of an array instead of on a single value as I do now:
<div class='row'>
<div class='col-lg-2 col-md-3 col-sm-6' ng-repeat="emp in empList | filter:{column1:zelectedColumn1,column2:zelectedColumn2,column3:zelectedColumn3} as res">
{{emp.column1}}
{{emp.column2}} <br>
{{emp.column3}} <br>
</div>
</div>
I have been struggling with this al day and hope someone can help me out here!

Having trouble dealing with Moment JS (Angular)

I am having trouble with Moment JS. Basically, I have some metadata for a radio station, and in my php call, I get in return the 'duration' of the song, the 'timestamp' when the song started.
I did some calculation with Moment JS to get the time when the song will be finished, and then I find the difference. However, the difference is returning a negative number, which then breaks the app.
If someone can help me that would be great.
This is my plunk http://plnkr.co/edit/joVLYdTKY5dZBOTNfTOI
Services
angular.module('starter', [])
.run(function(CurrentTrack){
CurrentTrack.refreshTrackData();
})
.controller('radioCtrl', function($scope,CurrentTrack) {
console.log(CurrentTrack);
$scope.CurrentTrack = CurrentTrack;
})
.service('CurrentTrack',function(radioData,$timeout){
var currentTrack = this;
this.setTrackData = function(trackData){
currentTrack.coverUrl = trackData.cover_url;
currentTrack.title = trackData.title;
currentTrack.artist = trackData.artist;
currentTrack.duration = moment.duration(parseInt(trackData.duration));
currentTrack.startedAt = moment.unix(trackData.timestamp);
currentTrack.finishesAt = moment(this.startedAt.add(this.duration));
currentTrack.updateIn = this.finishesAt.diff(moment());
currentTrack.refreshing = false;
return currentTrack.updateIn;
}
this.refreshTrackData = function(){
currentTrack.refreshing = true;
return radioData.refresh()
.then(currentTrack.setTrackData.bind(currentTrack))
.then(currentTrack.scheduleUpdate);
}
this.scheduleUpdate = function(ms){
console.log(ms)
$timeout(function(){
currentTrack.refreshTrackData()
},ms);
return;
}
})
Factory
.factory('radioData', function($http,$timeout) {
var retries = 0;
function parseResponse(response){
retries = 0;
if(!response.data.results){
console.log('no results')
return false;
}
console.log('refreshed...')
return response.data.results[0];
}
function makeRequest(){
console.log('refreshing...')
return $http.get('http://radio-sante-animale.fr/blah11.php? callback=jsonpCallback')
}
function retry(errResponse){
console.error('timed out');
//wait for a sec
retries++;
if(retries > 5){
throw new Error('timed out after 5 attempts!');
}
//oops
return $timeout(makeRequest,1000).then(null,retry);
}
var radioData = {
refresh: function() {
return makeRequest()
.then(null,retry)
.then(parseResponse)
.catch(function(err){
console.log(err);
});
}
};
return radioData;
});
From my side, it worked by adding Math.abs in your $timeout method
this.scheduleUpdate = function(ms) {
console.log( 'update in :' + ms)
$timeout(function() {
currentTrack.refreshTrackData()
}, Math.abs(ms));
return;
}
First I changed:
currentTrack.finishesAt = moment(this.startedAt.add(this.duration));
to:
currentTrack.finishesAt = moment(currentTrack.startedAt).add(currentTrack.duration);
In the original code you are mutating the startedAt time then cloning. I also changed all the this to currentTrack to make it more consistent.
The problem
The bug manifests when there is a difference in the time the server is keeping and the time that the client is keeping, (not because you have anything reversed).
Basically the server sends you trackData.timestamp which you parse and convert into a moment to use as your startedAt time. Then you change the trackData.duration into a moment.duration and add it to the startedAt time to get your finishesAt time.
If the time that the client is keeping is running ahead the server's time, the calculated finishesAt time will be earlier than when the actual song ends. An exaggerated example makes this more clear.
var app = angular.module('app', []);
app.controller('myController', function($scope, $timeout, $interval, Server) {
function updateTime() {
$scope.serverTime = moment().subtract(1,'s');
$scope.clientTime = moment();
}
function refreshInfo() {
Server.getSongInfo().then(parseInfo).then(scheduleRefresh);
};
function parseInfo(trackData) {
$scope.songInfo = trackData;
var startedAt = trackData.timestamp;
$scope.finishesAt = moment(startedAt).add(trackData.duration, 'ms');
$scope.updateIn = $scope.finishesAt.diff(moment());
return $scope.updateIn;
}
function scheduleRefresh(updateIn) {
if (updateIn < 0) {
$scope.bugged = true;
} else {
$scope.bugged = false;
}
$timeout(refreshInfo, updateIn);
}
$scope.songInfo = "loading";
updateTime();
refreshInfo();
$interval(updateTime, 1000);
});
app.service('Server', function($timeout, $interval) {
var song = {
duration: 5000
};
this.getSongInfo = function() {
return $timeout(function() { return this.trackData });
};
function nextSong() {
this.trackData = {
duration: song.duration,
timestamp: moment().subtract(1,'s')
};
}
nextSong();
$interval(nextSong, song.duration);
});
.container {
display: flex;
flex-flow: row wrap;
}
.row {
display: flex;
flex-direction: row;
flex-flow: space-around;
width: 100%;
}
.col {
margin: auto;
text-align: center;
}
.red {
background-color: red;
}
<head>
<script data-require="angular.js#1.4.0-beta.3" data-semver="1.4.0-beta.3" src="https://code.angularjs.org/1.4.0-beta.3/angular.js"></script>
<script data-require="moment.js#2.8.3" data-semver="2.8.3" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min.js"></script>
</head>
<div ng-app='app' ng-controller='myController'>
<div class="container">
<div class='row'>
<div class='col' ng-class='{red:bugged}'>
<h3>Song Info</h3>
<div>{{ songInfo }}</div>
</div>
</div>
<div class="row">
<div class='col'>
<h3>Server</h3>
<div>{{ serverTime.format("hh:mm:ss") }}</div>
</div>
<div class='col'>
<h3>Finishes At</h3>
<div>{{ finishesAt.format("hh:mm:ss") }}</div>
</div>
<div class='col'>
<h3>Update In</h3>
<div>{{ updateIn }}</div>
</div>
<div class='col'>
<h3>Client</h3>
<div>{{ clientTime.format("hh:mm:ss") }}</div>
</div>
</div>
</div>
</div>
Whenever the song info div is red, the client is in the loop where it is requesting new track info and parsing out a finish time that is earlier than the current moment, which immediately makes it request new track info.
angular.module('starter', [])
.run(function(CurrentTrack) {
CurrentTrack.refreshTrackData();
})
.controller('radioCtrl', function($scope, CurrentTrack) {
$scope.CurrentTrack = CurrentTrack;
})
.service('CurrentTrack', function(radioData, $timeout) {
var currentTrack = this;
this.setTrackData = function(trackData) {
currentTrack.coverUrl = trackData.cover_url;
currentTrack.title = trackData.title;
currentTrack.artist = trackData.artist;
currentTrack.duration = moment.duration(parseInt(trackData.duration));
currentTrack.startedAt = moment.unix(trackData.timestamp);
currentTrack.finishesAt = moment(currentTrack.startedAt).add(currentTrack.duration);
currentTrack.updateIn = currentTrack.finishesAt.diff(moment())
console.log(currentTrack, trackData);
currentTrack.refreshing = false;
return currentTrack.updateIn;
}
this.refreshTrackData = function() {
currentTrack.refreshing = true;
return radioData.refresh()
.then(currentTrack.setTrackData)
.then(currentTrack.scheduleUpdate);
}
this.scheduleUpdate = function(ms) {
console.log("update in " + ms)
$timeout(function() {
currentTrack.refreshTrackData()
}, ms);
return;
}
})
.factory('radioData', function($http, $timeout) {
var retries = 0;
function parseResponse(response) {
retries = 0;
if (!response.data.results) {
console.log('no results')
return false;
}
console.log('response parsed.')
return response.data.results[0];
}
function makeRequest() {
console.log('making request.')
return $http.get('http://radio-sante-animale.fr/blah11.php?callback=jsonpCallback')
}
function retry(errResponse) {
console.error('timed out');
//wait for a sec
retries++;
if (retries > 5) {
throw new Error('timed out after 5 attempts!');
}
//oops
return $timeout(makeRequest, 1000).then(parseResponse, retry);
}
var radioData = {
refresh: function() {
return makeRequest()
.then(parseResponse, retry)
.catch(function(err) {
console.log(err);
});
}
};
return radioData;
});
<!DOCTYPE html>
<html ng-app="starter">
<head>
<script data-require="angular.js#1.4.0-beta.3" data-semver="1.4.0-beta.3" src="https://code.angularjs.org/1.4.0-beta.3/angular.js"></script>
<script data-require="moment.js#2.8.3" data-semver="2.8.3" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min.js"></script>
</head>
<body ng-controller="radioCtrl">
<h3>Current Music Information</h3>
<p ng-show="CurrentTrack.refreshing">refreshing...</p>
<img style="width:300px;height:300px;" src="{{CurrentTrack.coverUrl}}" alt="">
<p>
Title : {{ CurrentTrack.title }}
<br />Artist : {{ CurrentTrack.artist }}
<br />The song started at {{ CurrentTrack.startedAt }}
<br />Duration of the song {{ CurrentTrack.duration.asSeconds() }} seconds
<br />Finishes At {{ CurrentTrack.finishesAt }}
<br />Update In {{ CurrentTrack.updateIn }}
<br />
</body>
</html>
I'm actually not sure the best approach to fixing this problem but hopefully some one has a better answer.
A hack solution is to add more time (some acceptable amount of error) to the finishesAt to give a little leeway.
currentTrack.finishesAt = moment(currentTrack.startedAt).add(currentTrack.duration).add(1, 's');
From http://momentjs.com/docs/#/displaying/difference/
If the moment is earlier than the moment you are passing to
moment.fn.diff, the return value will be negative.
You need to reverse the dates in your diff.
EDIT: Like this -
currentTrack.updateIn = moment().diff(this.finishesAt);

{{numPages}} not being calculated by pagination directive

I was under the impression with the pagination directive that the {{numPages}} value would be calculated by the directive. It isn't returning anything for me.
Is there anything I am missing to get this working properly? I don't want to have to calculate it, if the directive is supposed to be doing this for me. Otherwise paging is working great.
<pagination
total-items="totalItems"
ng-model="currentPage"
max-size="maxSize"
items-per-page="itemsPerPage"
class="pagination-sm"
boundary-links="true" rotate="false">
</pagination>
<table class="table table-striped">
<tr>
<td style="width:150px;">GPT ID</td>
<td style="width:250px;">Therapy Area</td>
<td style="width:450px;">GPT Description</td>
<td style="width:150px;">Actions</td>
</tr>
<tr ng-repeat="prGpt in prGpts | orderBy:['therapyArea.therapyArea','gptDesc'] | startFrom:(currentPage -1) * itemsPerPage | limitTo: itemsPerPage">
<td>{{prGpt.id}}</td>
<td>
<span ng-if="!prGpt.editMode">{{prGpt.therapyArea.therapyArea}}</span>
<span ng-if="prGpt.editMode && !createMode">
<select class="form-control" style="width:150px;" ng-model="selectedGpt.therapyArea" ng-options="item as item.therapyArea for item in therapyAreas"/>
</span>
</td>
<td>
<span ng-if="!prGpt.editMode">{{prGpt.gptDesc}}</span>
<span ng-if="prGpt.editMode && !createMode"><input class="form-control" type="text" style="width:400px;" ng-model="selectedGpt.gptDesc" /></span>
</td>
<td>
<span ng-if="!prGpt.editMode" class="glyphicon glyphicon-pencil" ng-click="copySelectedGpt(prGpt);beginEditGpt()"/>
<span ng-if="prGpt.editMode && !createMode" class="glyphicon glyphicon-floppy-disk" ng-click="saveEditGpt()"/>
<span ng-if="prGpt.editMode && !createMode" class="glyphicon glyphicon-thumbs-down" ng-click="cancelEditGpt()"/>
<span ng-if="!prGpt.editMode && !createMode" class="glyphicon glyphicon-remove-circle" ng-click="copySelectedGpt(prGpt);openDeleteDialog()"/>
<span><a ng-href="#!pr/gptProjects/{{prGpt.id}}">Edit Projects</a>
</span>
</tr>
</table>
<br/>
<pre>Page: {{currentPage}} / {{numPages}}</pre>
</div>
controller:
// GPT List Controller
.controller('prGPTCtrl',['$scope', '$modal', '$dialog', 'Restangular', 'prTAService', 'prGPTService', function($scope, $modal, $dialog, Restangular, prTAService, prGPTService) {
// window.alert('prGPTCtrl');
$scope.prGpts = {};
$scope.therapyAreas = {};
$scope.createMode = false;
$scope.selectedGpt = {};
$scope.newGpt = {};
// pagination
$scope.currentPage = 1;
$scope.itemsPerPage = 10;
$scope.maxSize = 20;
$scope.totalItems = $scope.prGpts.length;
Restangular.setBaseUrl('resources/pr');
//call the TA service to get the TA list for the drop down lists
//and then get the gpt list once successful
prTAService.getTAs().then(function(tas) {
$scope.therapyAreas = tas;
prGPTService.getGPTs().then(function(gpts) {
//window.alert('prGPTCtrl:getGPTs');
$scope.prGpts = gpts;
});
});
$scope.$watch('prGpts.length', function(){
$scope.totalItems = $scope.prGpts.length;
});
/*
* Take a copy of the selected GPT to copy in
*/
$scope.copySelectedGpt = function(prGpt) {
$scope.selectedGpt = Restangular.copy(prGpt);
};
$scope.beginEditGpt = function() {
var gpt = {};
var ta = {};
var gpt;
for(var i = 0; i < $scope.prGpts.length;i++) {
gpt = $scope.prGpts[i];
gpt.editMode = false;
}
var index = _.findIndex($scope.prGpts, function(b) {
return b.id === $scope.selectedGpt.id;
});
gpt = $scope.prGpts[index];
gpt.editMode = true;
var taIndex = _.findIndex($scope.therapyAreas, function(b) {
return b.id === $scope.selectedGpt.therapyArea.id;
});
ta = $scope.therapyAreas[taIndex];
$scope.selectedGpt.therapyArea = ta;
$scope.createMode = false;
};
$scope.cancelEditGpt = function() {
var gpt;
for(var i = 0; i < $scope.prGpts.length;i++) {
gpt = $scope.prGpts[i];
gpt.editMode = false;
}
var index = _.findIndex($scope.prGpts, function(b) {
return b.id === $scope.selectedGpt.id;
});
$scope.selectedGpt = null;
$scope.prGpts[index].editMode = false;
};
$scope.saveEditGpt = function() {
$scope.selectedGpt.save().then(function (gpt) {
// find the index in the array which corresponds to the current copy being edited
var index = _.findIndex($scope.prGpts, function(b) {
return b.id === $scope.selectedGpt.id;
});
$scope.prGpts[index] = $scope.selectedGpt;
$scope.prGpts[index].editMode = false;
$scope.selectedGpt = null;
},
function(err) {
window.alert('Error occured: ' + err);
}
);
};
// create a new GPT
$scope.createGpt = function() {
$scope.createMode = true;
var gpt;
for(var i = 0; i < $scope.prGpts.length;i++) {
gpt = $scope.prGpts[i];
gpt.editMode = false;
}
};
$scope.saveNewGpt = function() {
Restangular.all('/gpt/gpts').post($scope.newGpt).then(function(gpt) {
$scope.newGpt = {};
$scope.prGpts.push(gpt);
$scope.createMode = false;
// window.alert('created new GPT ' + gpt.gptDesc + ' with id ' + gpt.id);
});
};
$scope.openDeleteDialog = function() {
var title = "Please confirm deletion of GPT " + $scope.selectedGpt.gptDesc;
var msg = "<b>Delete GPT? Please confirm...</b>";
var btns = [{result:'CANCEL', label: 'Cancel'},
{result:'OK', label: 'OK', cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns, function(result) {
if (result === 'OK') {
$scope.deleteGpt();
}
});
};
$scope.deleteGpt = function() {
$scope.selectedGpt.remove().then(function() {
$scope.prGpts = _.without($scope.prGpts, _.findWhere($scope.prGpts, {id: $scope.selectedGpt.id}));
$scope.selectedGpt = null;
},
function() {
window.alert("There was an issue trying to delete GPT " + $scope.selectedGpt.gptDesc);
}
);
};
}]);
I have a startFrom filter.
.filter('startFrom', function () {
return function (input, start) {
if (input === undefined || input === null || input.length === 0
|| start === undefined || start === null || start.length === 0 || start === NaN) return [];
start = +start; //parse to int
try {
var result = input.slice(start);
return result;
} catch (e) {
// alert(input);
}
};
})
Regards
i
Looks like you're just missing num-pages="numPages" on your <pagination> tag. Essentially you have to provide a variable to pagination in which to return the number of pages. This is done via num-pages
<pagination
num-pages="numPages" <!-- Add this here -->
total-items="totalItems"
ng-model="currentPage"
max-size="maxSize"
items-per-page="itemsPerPage"
class="pagination-sm"
boundary-links="true" rotate="false">
</pagination>

How to do the Logic behind the next button in angularjs wizard

I have a customers.create.html partial bound to the WizardController.
Then I have 3 customers.create1,2,3.html partial files bound to WizardController1,2,3
Each WizardController1,2 or 3 has an isValid() function. This function determines wether the user can proceed to the next step.
The next button at the bottom of the pasted html should be disabed if ALL ? isValid() functions are false...
Thats my question but the same time that seems not correct to me.
I guess I am not doing the Wizard correctly...
Can someone please guide me how I should proceed with the architecture that the bottom next button is disabled when the current step isValid function returns false, please.
How can I make a connection from the WizardController to any of the WizardController1,2 or 3 ?
Is Firing an event like broadcast a good direction?
<div class="btn-group">
<button class="btn" ng-class="{'btn-primary':isCurrentStep(0)}" ng-click="setCurrentStep(0)">One</button>
<button class="btn" ng-class="{'btn-primary':isCurrentStep(1)}" ng-click="setCurrentStep(1)">Two</button>
<button class="btn" ng-class="{'btn-primary':isCurrentStep(2)}" ng-click="setCurrentStep(2)">Three</button>
</div>
<div ng-switch="getCurrentStep()" ng-animate="'slide'" class="slide-frame">
<div ng-switch-when="one">
<div ng-controller="WizardController1" ng-include src="'../views/customers.create1.html'"></div>
</div>
<div ng-switch-when="two">
<div ng-controller="WizardController2" ng-include src="'../views/customers.create2.html'"></div>
</div>
<div ng-switch-when="three">
<div ng-controller="WizardController3" ng-include src="'../views/customers.create3.html'"></div>
</div>
</div>
<a class="btn" ng-click="handlePrevious()" ng-show="!isFirstStep()">Back</a>
<a class="btn btn-primary" ng-disabled="" ng-click="handleNext(dismiss)">{{getNextLabel()}}</a>
'use strict';
angular.module('myApp').controller('WizardController', function($scope) {
$scope.steps = ['one', 'two', 'three'];
$scope.step = 0;
$scope.wizard = { tacos: 2 };
$scope.isFirstStep = function() {
return $scope.step === 0;
};
$scope.isLastStep = function() {
return $scope.step === ($scope.steps.length - 1);
};
$scope.isCurrentStep = function(step) {
return $scope.step === step;
};
$scope.setCurrentStep = function(step) {
$scope.step = step;
};
$scope.getCurrentStep = function() {
return $scope.steps[$scope.step];
};
$scope.getNextLabel = function() {
return ($scope.isLastStep()) ? 'Submit' : 'Next';
};
$scope.handlePrevious = function() {
$scope.step -= ($scope.isFirstStep()) ? 0 : 1;
};
$scope.handleNext = function(dismiss) {
if($scope.isLastStep()) {
dismiss();
} else {
$scope.step += 1;
}
};
});
durandalJS wizard sample code which could be used to rewrite a wizard for angularJS:
define(['durandal/activator', 'viewmodels/step1', 'viewmodels/step2', 'knockout', 'plugins/dialog', 'durandal/app', 'services/dataservice'],
function (activator, Step1, Step2, ko, dialog, app, service) {
var ctor = function (viewMode, schoolyearId) {
debugger;
if (viewMode === 'edit') {
service.editSchoolyear(schoolyearId);
}
else if (viewMode === 'create') {
service.createSchoolyear();
}
var self = this;
var steps = [new Step1(), new Step2()];
var step = ko.observable(0); // Start with first step
self.activeStep = activator.create();
var stepsLength = steps.length;
this.hasPrevious = ko.computed(function () {
return step() > 0;
});
self.caption = ko.observable();
this.activeStep(steps[step()]);
this.hasNext = ko.computed(function () {
if ((step() === stepsLength - 1) && self.activeStep().isValid()) {
// save
self.caption('save');
return true;
} else if ((step() < stepsLength - 1) && self.activeStep().isValid()) {
self.caption('next');
return true;
}
});
this.isLastStep = function() {
return step() === stepsLength - 1;
}
this.next = function() {
if (this.isLastStep()) {
$.when(service.createTimeTable())
.done(function () {
app.trigger('savedTimeTable', { isSuccess: true });
})
.fail(function () {
app.trigger('savedTimeTable', { isSuccess: false });
});
}
else if (step() < stepsLength) {
step(step() + 1);
self.activeStep(steps[step()]);
}
}
this.previous = function() {
if (step() > 0) {
step(step() - 1);
self.activeStep(steps[step()]);
}
}
}
return ctor;
});

ngClick not firing when $swipe is bound

I have an ngClick directive on elements that are also bound to $swipe. The ngClick doesn't fire (it was firing before the $swipe was added).
I'm using jQuery mobile combined with AngularJS.
Interestingly, my diagnostics show a swipe event with start and end the same - seems to contradict the minimum swipe distance, but perhaps the cancel function is being called. Possibly I could use the cancel function to find out where the click occurred but I feel I shouldn't have to do that.
The site is viewable at http://skimobile.cbdweb.net
HTML
<div id="matrix" data-role="page" ng-controller="matrixCtrl" ng-init="init()">
<div data-role="header">
<?php echo CHtml::encode($this->configs['SITENAME']); ?> Bookings
</div>
<div data-role="content">
<div class="lodgename noborder"> </div>
<div class="oneday onemonth" ng-repeat="m in months" ng-style="callMonthstyle(m)" ng-class="$last ? 'lastcol' : ''" on-finish-days>
{{monthNames[m.month]}} {{m.year}}
</div>
<br clear="both" />
<div class="lodgename noborder"> </div>
<div class="oneday" ng-style="callDaystyle()" ng-class="$last ? 'lastcol' : ''" ng-repeat="d in dates">
{{d.getDate()}}
</div>
<br clear="both" />
<div ng-repeat="lodge in data.lodges">
<div class="lodgename" ng-class="$last ? 'lastrow' : ''">{{lodge.lodgetitle}}</div>
<div class="oneday" ng-style="callDaystyle()" ng-class="($last ? 'lastcol' : '') + ' ' + ($parent.$last ? 'lastrow' : '')" ng-repeat="d in dates" ng-click="showDate(lodge, d)">
</div>
<br clear="both" />
</div>
<div ng-show="data.debug" style="margin-top: 20px;"
<ul>
<li>
move = {{swipe.move}}
</li>
<li>
start = {{swipe.start}}
</li>
<li>
end = {{swipe.end}}
</li>
<li>
scope.startDay = {{startDay}}
</li>
<li>
daysMoved = {{swipe.daysMoved}}
</li>
<li>
daysFinished = {{nDaysFinished}}
</li>
</ul>
</div>
<ul>
<?php foreach(Yii::app()->params['lodges'] as $lodgecode=>$lodge) {
echo "<li>" . $lodgecode . " = " . $lodge['lodgetitle'] . "</li>";
$lci = $this->lodgeconfigs[$lodgecode]['PREFIX_BOOKING_ID'];
echo "<LI>" . $lci . "</li>";
} ?>
</ul>
</div>
</div>
Javascript:
/* NG services */
var matrix = angular.module('Skimobile', ['ngTouch']);
matrix.factory('dayswidth', ['writeDays', function(){ // gets called on window change width
return function(scope, ww) {
var lodgename = $('.lodgename').first().width() + 4; // 4 = padding in lodgename class
var padding = 60 + lodgename;
var oldDisplayDays = scope.displayDays;
scope.displayDays = Math.min(28, (ww - padding)/scope.mindaywidth); // show at most 28 days, fewer if needed to maintain minimum width
scope.dayWidth = Math.floor( (ww-padding) / scope.displayDays );
if(oldDisplayDays!=scope.displayDays) { // avoid changing scope.dates as this will cause infinite recursion in the $digest on ng-repeat d in dates
scope.callWriteDays();
}
};
}]);
matrix.factory('writeDays', function() {
return function(scope) {
var d = new Date();
d.setTime(scope.startDay.getTime());
scope.dates = []; // repeat on this to draw days
scope.months = []; // repeat on this to draw months
var yearShown = false; // only show year once, when the month is long enough
var m = d.getMonth();
var daysLeft = 0; // days shown belonging to each month
for(i=0; i<scope.displayDays; i++) {
scope.dates.push(new Date(d.getTime()));
var oldd = new Date(d.getTime());
d.setDate(d.getDate()+1);
daysLeft++;
var newm = d.getMonth();
if(newm!=m) { // finished a month, display it
var newMonthObj = {month:m, days:daysLeft};
if(!yearShown && daysLeft*scope.dayWidth-1-4>3*scope.mindaywidth) {
newMonthObj.year = oldd.getFullYear();
yearShown = true;
} else {
newMonthObj.year = '';
}
scope.months.push(newMonthObj);
m = newm;
daysLeft = 0;
}
}
if(daysLeft>0) { // final month
newMonthObj = {month:m, days:daysLeft};
newMonthObj.year = yearShown ? '' : oldd.getFullYear();
scope.months.push(newMonthObj);
}
}
});
matrix.factory('daystyle', function(){
return function(scope) {
return {
'width': (scope.dayWidth-1) + 'px'
}; // -1 allows for border
}
});
matrix.factory('monthstyle', function(){
return function(scope, m) {
var days = m.days;
return {
'width': (scope.dayWidth*days-1-4) + 'px'
} // 1 for border, 4 for padding-left
}
});
matrix.directive('onFinishDays', function($timeout) {
return {
restrict: 'A',
link: function(scope, element, attr) {
if(scope.$last === true) { // without this it gets called once per visible month!
$timeout(function() {
scope.$emit('daysFinished');
})
}
}
}
});
/* NG controllers */
function matrixCtrl($scope, dayswidth, writeDays, daystyle, monthstyle, $swipe) {
$scope.callDayswidth = function(w){
dayswidth($scope, w);
};
$scope.callDaystyle = function() {
return daystyle($scope);
}
$scope.callMonthstyle = function(m) {
return monthstyle($scope, m);
}
$scope.callWriteDays = function() {
return writeDays($scope);
}
$scope.data = _main; // passed via Yii layout file
$scope.mindaywidth = 30;
$scope.monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var d = new Date(); // initially the matrix starts from today
$scope.startDay = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
var w = $(window);
$scope.getWindowDimensions = function() {
return {'h': w.height(), 'w': w.width()};
};
$scope.$watch($scope.getWindowDimensions, function(newValue, oldValue){
$scope.callDayswidth(newValue.w);
}, true);
w.bind('resize', function() {
$scope.$apply();
})
$scope.showDate = function(lodge, d){
alert(lodge.lodgetitle + ' ' + d.getDate());
}
$scope.swipe = {};
$scope.nDaysFinished = 0;
$scope.$on('daysFinished', function(event) {
$scope.nDaysFinished++;
$swipe.bind($('.oneday'), {
end:function(loc){
$scope.swipe.end = loc.x;
$scope.swipe.daysMoved = Math.floor((loc.x - $scope.swipe.start) / $scope.dayWidth);
$scope.startDay.setTime($scope.startDay.getTime() - $scope.swipe.daysMoved*24*60*60*1000); // compute new start day at end of drag;
$scope.callWriteDays();
$scope.$apply();
}
})
});
}

Resources