Given the following code, I'm finding that once the loadDetails function (the last one) is being triggered, the ID variable comes back undefined, as on the other functions is coming back correctly.
Did I miss something?
function Ctrl($scope, $http) {
var search = function(name) {
if (name) {
$http.get('http://api.discogs.com/database/search?type=artist&q='+ name +'&page=1&per_page=7', {ignoreLoadingBar: true}).
success(function(data3) {
$scope.clicked = false;
$scope.results = data3.results;
});
}
$scope.reset = function () {
$scope.sliding = false;
$scope.name = undefined;
}
}
$scope.$watch('name', search, true);
$scope.getDetails = function (id) {
$http.get('http://api.discogs.com/artists/' + id).
success(function(data) {
$scope.artist = data;
});
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=8').
success(function(data2) {
$scope.releases = data2.releases;
});
$scope.$watch(function() {
return $scope.artist;
}, function() {
var pos = $scope.artist.name.toLowerCase().indexOf(', the');
if (pos != -1) {
$scope.artist.name = 'The ' + $scope.artist.name.slice(0, pos);
}
});
var _page = 0;
$scope.releases = [];
$scope.loadDetails = function(id) {
_page++;
console.log(_page);
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=' + _page + '&per_page=12').then(function(data2) {
$scope.releases = data2.releases;
});
};
$scope.clicked = true;
$scope.sliding = true;
}
EDIT: Here's my view code:
<div class="infinite" infinite-scroll="loadDetails(artist.id)">
<div class="col-xs-3 col-md-3 release" ng-controller="ImageCtrl" release="release" ng-repeat="release in releases | filter:album | filter:year" the-directive position="{{ $index + 1 }}" last="{{ $last }}">
<img class="img-responsive" ng-src="{{image}}"/> {{release.title | characters:45}}
</div>
<div style='clear: both;'></div>
</div>
And the ng-Infinite-Scroll script that triggers the function when the containing div reaches the bottom:
/* ng-infinite-scroll - v1.0.0 - 2013-02-23 */
var mod;
mod = angular.module('infinite-scroll', []);
mod.directive('infiniteScroll', [
'$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
return {
link: function(scope, elem, attrs) {
var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
$window = angular.element($window);
scrollDistance = 0;
if (attrs.infiniteScrollDistance != null) {
scope.$watch(attrs.infiniteScrollDistance, function(value) {
return scrollDistance = parseInt(value, 10);
});
}
scrollEnabled = true;
checkWhenEnabled = false;
if (attrs.infiniteScrollDisabled != null) {
scope.$watch(attrs.infiniteScrollDisabled, function(value) {
scrollEnabled = !value;
if (scrollEnabled && checkWhenEnabled) {
checkWhenEnabled = false;
return handler();
}
});
}
handler = function() {
var elementBottom, remaining, shouldScroll, windowBottom;
windowBottom = $window.height() + $window.scrollTop();
elementBottom = elem.offset().top + elem.height();
remaining = elementBottom - windowBottom;
shouldScroll = remaining <= $window.height() * scrollDistance;
if (shouldScroll && scrollEnabled) {
if ($rootScope.$$phase) {
return scope.$eval(attrs.infiniteScroll);
} else {
return scope.$apply(attrs.infiniteScroll);
}
} else if (shouldScroll) {
return checkWhenEnabled = true;
}
};
$window.on('scroll', handler);
scope.$on('$destroy', function() {
return $window.off('scroll', handler);
});
return $timeout((function() {
if (attrs.infiniteScrollImmediateCheck) {
if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
return handler();
}
} else {
return handler();
}
}), 0);
}
};
}
]);
rSo from what I read I understand you problem as in loadDetails() the id parameter is undefined. Where is loadDetails() called from? I assume its being called from the view. Are you passing this param in when it is being called? For ex:
<button ng-click="loadDetails('myId')">Load Details</button>
I would say your issue is you are not passing the param to this function. It would be helpful if you posted the view associated with this controller.
Related
Below directive is used for getting data continuously from server with default interval of 5 sec
app.directive('livestatDataGrid', livestatDataGrid);
function livestatDataGrid($http, $interval, $window) {
return {
restrict: 'E',
templateUrl: 'directives/livestatDataGrid/livestat-datagrid.html',
scope: {
viewName: '=viewName',
gridOptions: '=',
deleteItem: '=deleteItem'
},
link: function($scope) {
var defaultSec = 5;
$scope.timerObj = {};
$scope.timerObj.selectedItem = defaultSec;
$scope.timerObj.isDisplay = true;
var timer = null;
$scope.copyVal = defaultSec;
$scope.getViewData = function(viewName) {
console.log('Came here !! Directive ', viewName);
$http.get('json/' + viewName + '.json').then(function success(response) {
$scope.metricHeader = response.data.metricHeader;
$scope.stats = response.data.stats;
}, function error(error) {
console.log("Eooor ", error)
});
};
$scope.getViewData($scope.viewName);
// Timer code
$scope.editTimer = function() {
$scope.timerObj.isDisplay = false;
};
$scope.setTimer = function() {
if ($scope.timerObj.selectedItem && isNaN($scope.timerObj.selectedItem)) {
$scope.timerObj.selectedItem = defaultSec;
}
if ($scope.copyVal != "" && $scope.copyVal == $scope.timerObj.selectedItem) {
$scope.timerObj.isDisplay = true;
return;
}
$scope.StopTimer();
$scope.copyVal = angular.copy($scope.timerObj.selectedItem);
$scope.timerObj.isDisplay = true;
$scope.startTimer();
};
// starting the timer here once directive is initialized.Using `$interval`
$scope.startTimer = function() {
console.log("in timer " + $scope.timerObj.selectedItem);
if ($scope.timerObj.selectedItem && $scope.viewName) {
//time interval for sending request to server
timer = $interval(function() {
if ($scope.viewName)
$scope.getViewData($scope.viewName);
$interval.cancel(this);
console.log(timer)
}, $scope.timerObj.selectedItem * 1000);
}
};
$scope.startTimer();
// Once the job is done, destroy the timer using `$interval.cancel`.
$scope.StopTimer = function() {
if (angular.isDefined(timer)) {
console.log("in stop timer");
$interval.cancel(timer);
}
};
//timer code ends her
}
}
}
Not clear what do you intend to ask/discuss. Is this a self answer question?
I have a function and i did it in this way
JS :
function updateInstitution (isValid) {alert('hi')
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.form.institutionForm');
return false;
}
var data = JSON.stringify(vm.institution);
httpService.put('institutions/' + vm.institution_id, data).then(function (results) {
if (results && results.data && results.data.details) {
vm.institution = results.data.details;
formInstitutionData('profile');
commonService.showNotification('success', 'Institution Details updated successfully!');
$('#institutionModal').modal('hide');
}
});
}
}
vm.updateInstitution = updateInstitution;
Html :
<button type="button" class="btn btn-blue" ng-click="vm.updateInstitution(vm.form.institutionForm.$valid)" ng-bind="field.saveText"></button>
But i am getting the error as
updateInstitution is not defined
Can anyone please suggest help.Thanks.
JS :
(function () {
'use strict';
// Institutions controller
angular
.module('institutions')
.controller('InstitutionsController', InstitutionsController);
InstitutionsController.$inject = ['$scope', '$state', '$window', '$timeout', 'httpService', 'Authentication', 'commonService'];
function active() {
httpService.get('institutions/' + vm.institution_id).then(function (results) {
if (results && results.data && results.data.details) {
vm.institutionCopyData = angular.copy(results.data.details);
formInstitutionData('all');
}
});
}
$scope.editInstitutionModal = function (type) {
$scope.field = {};
$scope.showInstitutionModal = false;
if (type === 'basicedit') {
$scope.field.field_type = 'edit-institution.form.client';
$scope.field.formName = 'Edit institution (' + vm.institutionObj.name + ')';
$scope.field.saveText = 'Update';
}
if(type === 'general'){
$scope.field.field_type = 'add-general.form.client';
$scope.field.formName = 'General Info';
$scope.field.saveText = 'Save';
}
$timeout(function () {
$scope.showInstitutionModal = true;
$('#institutionModal').modal('show');
$scope.$apply();
}, 10);
};
function updateInstitution (isValid) {alert('hi')
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.form.institutionForm');
return false;
}
var data = JSON.stringify(vm.institution);
httpService.put('institutions/' + vm.institution_id, data).then(function (results) {
if (results && results.data && results.data.details) {
vm.institution = results.data.details;
formInstitutionData('profile');
commonService.showNotification('success', 'Institution Details updated successfully!');
$('#institutionModal').modal('hide');
}
});
}
}
}
}());
But i am getting the error as
updateInstitution is not defined
Can anyone please suggest help.Thanks.
But i am getting the error as
updateInstitution is not defined
Can anyone please suggest help.Thanks.
You should declare the following in your controller:
var vm = this;
vm.updateInstitution = updateInstitution;
you use $scope.updateInstitution instead of function updateInstitution() , because communication bridge between html and controller is $scope,
or for use vm.function you defined $scope.vm
$scope.updateInstitution = function(){
//Your code
}
I am using a directive for star rating. But the template the is loaded before data is loaded from HTTP. So i want to reload directive template after HTTP request is successful.
HTML
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js" type="text/javascript"></script>
</head><body>
<div ng-app="myapp" ng-controller="movieCtrl">
<div star-rating rating="starRating" read-only="false" max-rating="10" click="click(param)" mouse-hover="mouseHover(param)"
mouse-leave="mouseLeave(param)"></div>
</div></body></html>
JS
var app = angular.module('myapp', []);
app.controller("movieCtrl", function($scope, $http) {
$scope.starRating = 0;
$scope.hoverRating = 0;
$scope.mouseHover = function(param) {
$scope.hoverRating1 = param;
};
$scope.mouseLeave = function(param) {
$scope.hoverRating1 = param + '*';
};
//problem here
//actual data coming via http
//when value is changed i want to re-render below directive template
setTimeout(function() {
$scope.starRating = 5
}, 1000);
});
app.directive('starRating', function() {
return {
scope: {
rating: '=',
maxRating: '#',
readOnly: '#',
click: "&",
mouseHover: "&",
mouseLeave: "&"
},
restrict: 'EA',
template: "<div style='display: inline-block; margin: 0px; padding: 0px; cursor:pointer;' ng-repeat='idx in maxRatings track by $index'> \
<img ng-src='{{((hoverValue + _rating) <= $index) && \"http://www.codeproject.com/script/ratings/images/star-empty-lg.png\" || \"http://www.codeproject.com/script/ratings/images/star-fill-lg.png\"}}' \
ng-Click='isolatedClick($index + 1)' \
ng-mouseenter='isolatedMouseHover($index + 1)' \
ng-mouseleave='isolatedMouseLeave($index + 1)'></img> \
</div>",
compile: function(element, attrs) {
if (!attrs.maxRating || (Number(attrs.maxRating) <= 0)) {
attrs.maxRating = '5';
};
},
controller: function($scope, $element, $attrs) {
$scope.maxRatings = [];
for (var i = 1; i <= $scope.maxRating; i++) {
$scope.maxRatings.push({});
};
$scope._rating = $scope.rating;
$scope.isolatedClick = function(param) {
if ($scope.readOnly == 'true') return;
$scope.rating = $scope._rating = param;
$scope.hoverValue = 0;
$scope.click({
param: param
});
};
$scope.isolatedMouseHover = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = 0;
$scope.hoverValue = param;
$scope.mouseHover({
param: param
});
};
$scope.isolatedMouseLeave = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = $scope.rating;
$scope.hoverValue = 0;
$scope.mouseLeave({
param: param
});
};
}
};
});
See Codepen for more info.
Here is a simple rating directive which uses stars, note that the logic is in the link function, rather than the controller.
function starRating() {
return {
restrict: 'EA',
template:
'<ul class="star-rating" ng-class="{readonly: readonly}">' +
// see ng-repeat here? this will update when scope.stars is updated
' <li ng-repeat="star in stars" class="star" ng-class="{filled: star.filled}" ng-click="toggle($index)">' +
' <i class="fa fa-star"></i>' + // or ★
' </li>' +
'</ul>',
scope: {
ratingValue: '=ngModel',
max: '=?', // optional (default is 5)
onRatingSelect: '&?', // callback
readonly: '=?' // set whether this should be changeable or not
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
function updateStars() { // update to rating value
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
scope.toggle = function(index) {
if (scope.readonly == undefined || scope.readonly === false){
scope.ratingValue = index + 1;
scope.onRatingSelect({
rating: index + 1
});
}
};
scope.$watch('ratingValue', function(oldValue, newValue) {
if (newValue) {
updateStars();
}
});
}
};
}
Use $scope.$apply() on setTimeout function and your code will work fine
also i have made simple modification to your code .. check here
i created a service to share data b/n controllers
added some $watch function to detect value change
var app = angular.module('myapp', []);
app.controller("movieCtrl", function($scope, $http, share) {
$scope.starRating = 0;
$scope.hoverRating = 0;
$scope.mouseHover = function(param) {
$scope.hoverRating1 = param;
};
$scope.mouseLeave = function(param) {
$scope.hoverRating1 = param + '*';
};
$scope.$watch('starRating', function() {
share.rating = $scope.starRating
});
setTimeout(function() {
console.log('timeout set');
$scope.starRating = 5;
$scope.$apply();
}, 1000);
});
app.factory('share', function() {
var obj = {
rating: 0
}
return obj;
});
app.directive('starRating', function() {
return {
scope: {
rating: '=',
maxRating: '#',
readOnly: '#',
click: "&",
mouseHover: "&",
mouseLeave: "&"
},
restrict: 'EA',
templateUrl: "star1.html",
compile: function(element, attrs) {
if (!attrs.maxRating || (Number(attrs.maxRating) <= 0)) {
attrs.maxRating = '5';
};
},
controller: function($scope, $element, $attrs, share) {
$scope.maxRatings = [];
$scope.rating = share.rating;
$scope.$watch('rating', function() {
$scope._rating = share.rating;
});
for (var i = 1; i <= $scope.maxRating; i++) {
$scope.maxRatings.push({});
};
$scope._rating = share.rating;
$scope.isolatedClick = function(param) {
if ($scope.readOnly == 'true') return;
$scope.rating = $scope._rating = param;
$scope.hoverValue = 0;
$scope.click({
param: param
});
};
$scope.isolatedMouseHover = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = 0;
$scope.hoverValue = param;
$scope.mouseHover({
param: param
});
};
$scope.isolatedMouseLeave = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = $scope.rating;
$scope.hoverValue = 0;
$scope.mouseLeave({
param: param
});
};
}
};
});
I edited the TypeAhead directive that is part of Angular UI for AngularJS so it will only give suggestions based on the most recent word, delimited by space (" ").
I intend to use it to for something like a query builder, dynamically giving suggestions based on surrounding syntax. This works as expected for the first word, but once we get to the second word, the promise does not resolve anymore for some reason. The value of inputValue is correct and as expected, but the code inside
$q.when(parserResult.source(originalScope, locals)).then(function (matches) {
does not appear to get run. Please advise.
My code (exactly the same as original except for I added a function called getLastWord that truncates the current expression :
angular.module('customTypeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
.factory('customTypeaheadParser', ['$parse', function ($parse) {
// 00000111000000000000022200000000000000003333333333333330000000000044000
var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
return {
parse: function (input) {
var match = input.match(TYPEAHEAD_REGEXP);
if (!match) {
throw new Error(
'Expected customTypeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
' but got "' + input + '".');
}
return {
itemName: match[3],
source: $parse(match[4]),
viewMapper: $parse(match[2] || match[1]),
modelMapper: $parse(match[1])
};
}
};
}])
.directive('customTypeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$rootScope', '$position', 'customTypeaheadParser',
function ($compile, $parse, $q, $timeout, $document, $rootScope, $position, customTypeaheadParser) {
var HOT_KEYS = [9, 13, 27, 38, 40];
return {
require: 'ngModel',
link: function (originalScope, element, attrs, modelCtrl) {
//SUPPORTED ATTRIBUTES (OPTIONS)
//minimal no of characters that needs to be entered before customTypeahead kicks-in
var minLength = originalScope.$eval(attrs.customTypeaheadMinLength);
if (!minLength && minLength !== 0) {
minLength = 0;
}
//minimal wait time after last character typed before customTypeahead kicks-in
var waitTime = originalScope.$eval(attrs.customTypeaheadWaitMs) || 0;
//should it restrict model values to the ones selected from the popup only?
var isEditable = originalScope.$eval(attrs.customTypeaheadEditable) !== false;
//binding to a variable that indicates if matches are being retrieved asynchronously
var isLoadingSetter = $parse(attrs.customTypeaheadLoading).assign || angular.noop;
//a callback executed when a match is selected
var onSelectCallback = $parse(attrs.customTypeaheadOnSelect);
var inputFormatter = attrs.customTypeaheadInputFormatter ? $parse(attrs.customTypeaheadInputFormatter) : undefined;
var appendToBody = attrs.customTypeaheadAppendToBody ? originalScope.$eval(attrs.customTypeaheadAppendToBody) : false;
var focusFirst = originalScope.$eval(attrs.customTypeaheadFocusFirst) !== false;
//INTERNAL VARIABLES
//model setter executed upon match selection
var $setModelValue = $parse(attrs.ngModel).assign;
//expressions used by customTypeahead
var parserResult = customTypeaheadParser.parse(attrs.customTypeahead);
var hasFocus;
//create a child scope for the customTypeahead directive so we are not polluting original scope
//with customTypeahead-specific data (matches, query etc.)
var scope = originalScope.$new();
originalScope.$on('$destroy', function () {
scope.$destroy();
});
// WAI-ARIA
var popupId = 'customTypeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
element.attr({
'aria-autocomplete': 'list',
'aria-expanded': false,
'aria-owns': popupId
});
//pop-up element used to display matches
var popUpEl = angular.element('<div custom-typeahead-popup></div>');
popUpEl.attr({
id: popupId,
matches: 'matches',
active: 'activeIdx',
select: 'select(activeIdx)',
query: 'query',
position: 'position'
});
//custom item template
if (angular.isDefined(attrs.customTypeaheadTemplateUrl)) {
popUpEl.attr('template-url', attrs.customTypeaheadTemplateUrl);
}
var resetMatches = function () {
scope.matches = [];
scope.activeIdx = -1;
element.attr('aria-expanded', false);
};
var getMatchId = function (index) {
return popupId + '-option-' + index;
};
// Indicate that the specified match is the active (pre-selected) item in the list owned by this customTypeahead.
// This attribute is added or removed automatically when the `activeIdx` changes.
scope.$watch('activeIdx', function (index) {
if (index < 0) {
element.removeAttr('aria-activedescendant');
} else {
element.attr('aria-activedescendant', getMatchId(index));
}
});
var getLastWord = function (expression) {
if (expression === "") {
return "";
}
var temp = expression.split(" ");
return temp[temp.length - 1];
};
var getMatchesAsync = function (inputValue) {
inputValue = getLastWord(inputValue);
var locals = {$viewValue: inputValue};
isLoadingSetter(originalScope, true);
$q.when(parserResult.source(originalScope, locals)).then(function (matches) {
//it might happen that several async queries were in progress if a user were typing fast
//but we are interested only in responses that correspond to the current view value
var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
if (onCurrentRequest && hasFocus) {
if (matches && matches.length > 0) {
scope.activeIdx = focusFirst ? 0 : -1;
scope.matches.length = 0;
//transform labels
for (var i = 0; i < matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = inputValue;
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
//due to other elements being rendered
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
element.attr('aria-expanded', true);
} else {
resetMatches();
}
}
if (onCurrentRequest) {
isLoadingSetter(originalScope, false);
}
}, function () {
resetMatches();
isLoadingSetter(originalScope, false);
});
};
resetMatches();
//we need to propagate user's query so we can highlight matches
scope.query = undefined;
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
var timeoutPromise;
var scheduleSearchWithTimeout = function (inputValue) {
timeoutPromise = $timeout(function () {
getMatchesAsync(inputValue);
}, waitTime);
};
var cancelPreviousTimeout = function () {
if (timeoutPromise) {
$timeout.cancel(timeoutPromise);
}
};
//plug into $parsers pipeline to open a customTypeahead on view changes initiated from DOM
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
modelCtrl.$parsers.unshift(function (inputValue) {
inputValue = getLastWord(inputValue);
hasFocus = true;
if (minLength === 0 || inputValue && inputValue.length >= minLength) {
if (waitTime > 0) {
cancelPreviousTimeout();
scheduleSearchWithTimeout(inputValue);
} else {
getMatchesAsync(inputValue);
}
} else {
isLoadingSetter(originalScope, false);
cancelPreviousTimeout();
resetMatches();
}
if (isEditable) {
return inputValue;
} else {
if (!inputValue) {
// Reset in case user had typed something previously.
modelCtrl.$setValidity('editable', true);
return inputValue;
} else {
modelCtrl.$setValidity('editable', false);
return undefined;
}
}
});
modelCtrl.$formatters.push(function (modelValue) {
var candidateViewValue, emptyViewValue;
var locals = {};
// The validity may be set to false via $parsers (see above) if
// the model is restricted to selected values. If the model
// is set manually it is considered to be valid.
if (!isEditable) {
modelCtrl.$setValidity('editable', true);
}
if (inputFormatter) {
locals.$model = modelValue;
return inputFormatter(originalScope, locals);
} else {
//it might happen that we don't have enough info to properly render input value
//we need to check for this situation and simply return model value if we can't apply custom formatting
locals[parserResult.itemName] = modelValue;
candidateViewValue = parserResult.viewMapper(originalScope, locals);
locals[parserResult.itemName] = undefined;
emptyViewValue = parserResult.viewMapper(originalScope, locals);
return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
}
});
scope.select = function (activeIdx) {
//called from within the $digest() cycle
var locals = {};
var model, item;
locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
model = parserResult.modelMapper(originalScope, locals);
$setModelValue(originalScope, model);
modelCtrl.$setValidity('editable', true);
modelCtrl.$setValidity('parse', true);
onSelectCallback(originalScope, {
$item: item,
$model: model,
$label: parserResult.viewMapper(originalScope, locals)
});
resetMatches();
//return focus to the input element if a match was selected via a mouse click event
// use timeout to avoid $rootScope:inprog error
$timeout(function () {
element[0].focus();
}, 0, false);
};
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
element.bind('keydown', function (evt) {
//customTypeahead is open and an "interesting" key was pressed
if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
return;
}
// if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything
if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) {
return;
}
evt.preventDefault();
if (evt.which === 40) {
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();
} else if (evt.which === 38) {
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();
} else if (evt.which === 13 || evt.which === 9) {
scope.$apply(function () {
scope.select(scope.activeIdx);
});
} else if (evt.which === 27) {
evt.stopPropagation();
resetMatches();
scope.$digest();
}
});
element.bind('blur', function (evt) {
hasFocus = false;
});
// Keep reference to click handler to unbind it.
var dismissClickHandler = function (evt) {
if (element[0] !== evt.target) {
resetMatches();
if (!$rootScope.$$phase) {
scope.$digest();
}
}
};
$document.bind('click', dismissClickHandler);
originalScope.$on('$destroy', function () {
$document.unbind('click', dismissClickHandler);
if (appendToBody) {
$popup.remove();
}
// Prevent jQuery cache memory leak
popUpEl.remove();
});
var $popup = $compile(popUpEl)(scope);
if (appendToBody) {
$document.find('body').append($popup);
} else {
element.after($popup);
}
}
};
}])
.directive('customTypeaheadPopup', function () {
return {
restrict: 'EA',
scope: {
matches: '=',
query: '=',
active: '=',
position: '&',
select: '&'
},
replace: true,
templateUrl: 'html/templates/custom-typeahead-popup.html',
link: function (scope, element, attrs) {
scope.templateUrl = attrs.templateUrl;
scope.isOpen = function () {
return scope.matches.length > 0;
};
scope.isActive = function (matchIdx) {
return scope.active == matchIdx;
};
scope.selectActive = function (matchIdx) {
scope.active = matchIdx;
};
scope.selectMatch = function (activeIdx) {
scope.select({activeIdx: activeIdx});
};
}
};
})
.directive('customTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) {
return {
restrict: 'EA',
scope: {
index: '=',
match: '=',
query: '='
},
link: function (scope, element, attrs) {
var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'html/templates/custom-typeahead-match.html';
$templateRequest(tplUrl).then(function (tplContent) {
$compile(tplContent.trim())(scope, function (clonedElement) {
element.replaceWith(clonedElement);
});
});
}
};
}])
.filter('customTypeaheadHighlight', function () {
function escapeRegexp(queryToEscape) {
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
}
return function (matchItem, query) {
return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
};
});
I coded the below directive for infinite scroll, my problem which I couldn't figure out why it just fire once when the directive is loaded, I need your advice on how to make my list infinite-scroll.
I'm using it to get data remotely and each time i'm calling it I add to the counter 25, so each time it would return more data.
Thanx,
angular.module('MyApp')
.controller('InboxCtrl', function($scope, InboxFactory) {
var counter = 0;
$scope.loadData = function() {
var promise = InboxFactory.getEvents(counter);
promise.then(function(result) {
$scope.events = result;
});
counter += 25;
};
});
angular.module('MyApp')
.factory('InboxFactory', function($http, $q) {
// Service logic
var defered = $q.defer();
function getUrl(count) {
return "api/inbox/get?request={'what':'Search','criteria':'inbox','criteriaId':null,'startTime':null,'endTime':null,'offset':" + count + ",'limit':25,'order':'event_time','direction':'DESC','source':''}";
}
function extract(result) {
return result.data.data;
}
// Public API here
return {
getEvents: function(count) {
$http.get(getUrl(count)).then(
function(result) {
defered.resolve(extract(result))
}, function(err) {
defered.reject(err);
}
);
return defered.promise;
}
};
});
angular.module('MyApp')
.directive('infiniteScroll', ['$timeout',
function(timeout) {
return {
link: function(scope, element, attr) {
var
lengthThreshold = attr.scrollThreshold || 50,
timeThreshold = attr.timeThreshold || 400,
handler = scope.$eval(attr.infiniteScroll),
promise = null,
lastRemaining = 9999;
lengthThreshold = parseInt(lengthThreshold, 10);
timeThreshold = parseInt(timeThreshold, 10);
if (!handler || !components.isFunction(handler)) {
handler = components.noop;
}
element.bind('scroll', function() {
var
remaining = element[0].scrollHeight - (element[0].clientHeight + element[0].scrollTop);
//if we have reached the threshold and we scroll down
if (remaining < lengthThreshold && (remaining - lastRemaining) < 0) {
//if there is already a timer running which has no expired yet we have to cancel it and restart the timer
if (promise !== null) {
timeout.cancel(promise);
}
promise = timeout(function() {
handler();
promise = null;
}, timeThreshold);
}
lastRemaining = remaining;
});
}
};
}
]);
<ul class="inbox-list" infinite-scroll="loadData()">
<li class="clearfix" ng-repeat="event in events">{{event}}</li>
</ul>
I Made some changes the more important is the use of ng-transclude and the creation of a new scope for the directive to pass the method and the parameters. You can have a look at the jsbind. Of course the data are hard coded so i could fake the behaviour.
<ul class="inbox-list" my-infinite-scroll composite-method="loadData()">