I am using infinite scroll directive and this is the code:
angApp.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;
console.log($window);
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);
}
};
}
]);
The problem with this code is sometimes it works sometimes it doesn't. If I do a hard refresh by pressing Ctrl + F5 it most certainly throws the below error.
This is the error:
I am using Firefox 29.0.1. What am I missing?
Normally this should work, but there might be some problem with requireJS. Using $() instead should ensure you are using Jquery. You might need the order plug-in for requireJS since normally RequireJS loads and evaluates scripts in an undetermined order.
Related
I am using CK-Editor 4. In which I have created Directive. When I click on Textarea my cursor pointer is Move to First position. I want my cursor position should be available when I click in any of the Place inside Textarea. It should not be able to move first or last position until I click on last position.
vpb.directive('ckeInline', function ($timeout, $parse, $rootScope,$) {
return {
require: "ngModel",
link: function (scope, element, attrs, ngModel) {
if (attrs.ckeInlineShowtitle) ngModel = "";
isMobile = $(".ipad, .ipod, .android").length > 0;
var destroy_this_editor = function(id)
{
setTimeout(function () {
try{
CKEDITOR.instances[id].destroy(true);
} catch (ex) {
}
},500)
}
if (!isMobile) {
var create_and_focus = function () {
if (scope.setup.edit_mode || attrs.ckeNonEditMode) {
attrs.$set('contenteditable', true);
var a_id = attrs.ckeInlineId;
var menu_selector = attrs.ckeInlineMenu;
//get editor
//create editor if doesn't exist
var e = CKEDITOR.instances[a_id];
if (!e) {
element.menu = $(menu_selector);
//set up behavior for menu
uif.wire_up_menu(element, scope);
//hide all menu bars
$("nav.text-editor-menu-bar").hide();
//create editor
var config = {
toolbar: "More",
on: {
'blur': function () {
save_editor_content(function () {
destroy_this_editor(a_id);
});
},
'focusout': function () {
save_editor_content(function () {
destroy_this_editor(a_id);
});
},
'focus': function () {
//show the current menu
element.menu.show();
//fix menu width
uif.stretch_menu(element);
},
'instanceReady': function (event,element) {
//----------------I think,Here i want logic to set caret sign or set mouse
//pointer to appropriate place
// event.editor.focus();
// element.focus();
}
}
};
if (attrs.ckeInlineToolbar) {
config.toolbar = attrs.ckeInlineToolbar;
}
var editor = CKEDITOR.inline(a_id, config);
if (attrs.ckeInlineOnInit) {
uif.apply_scope($rootScope, $parse(attrs.ckeInlineOnInit));
}
}
else
{
e.focus();
element.focus();
}
} else
{
attrs.$set('contenteditable', false);
}
}
element.click(function () {
create_and_focus();
});
if (attrs.ckeInlineFocusWatch) {
scope.$watch(function () {
return $parse(attrs.ckeInlineFocusWatch);
}, function () {
if (attrs.ckeInlineFocusCondition == undefined || $parse(attrs.ckeInlineFocusCondition)() == true) {
create_and_focus();
}
})
}
var saving = false;
var save_editor_content = function (cb) {
if (!saving)
{
saving = true;
var a_id = attrs.ckeInlineId;
if (a_id) {
var editor = CKEDITOR.instances[a_id];
if (editor) {
var menu_selector = attrs.ckeInlineMenu;
element.menu.hide();
//attrs.$set('contenteditable', false);
var newValue = editor.getData().replace(/ /, ' ');
if (ngModel.$modelValue != newValue) {
if (attrs.ckeInlineBlurBool) {
$parse(attrs.ckeInlineBlurBool).assign($rootScope, false);
}
ngModel.$setViewValue(newValue);
if (attrs.ckeInlineSave) {
uif.apply_scope(scope, $parse(attrs.ckeInlineSave));
}
$timeout(function () {
saving = false;
if (cb) cb();
}, 1100)
} else
{
saving = false;
$rootScope.setup.blcok_edits = false;
$rootScope.setup.block_all_editors = false;
if (cb) cb();
}
}
}
else
{
saving = false;
}
}
};
} else if (attrs.ckeNonEditMode) {
attrs.$set('contenteditable', true);
element.blur(function () {
ngModel.$setViewValue(element.html().replace(/ /, ' '));
if (attrs.ckeInlineSave) {
uif.apply_scope(scope, $parse(attrs.ckeInlineSave));
}
})
}
}
}
});
I am using the following Angular directive to hide div elements when the user clicks or touches outside.
https://github.com/TheSharpieOne/angular-off-click
It is working as expected but when you click outside of a div on a button that toggles the div the 'angular-off-click' callback is fired to hide the container but then the toggle function attached to the button is called, reopening the div.
The 'off-click-filter' solves this by adding exceptions which use css selectors to check before the hide function is called. However this was removed as we did not want the extra bloat of css class exceptions in the html markup.
The desired function is for the toggle button not to fire its handler when you click outside of the container
Update
It is only a problem on touch devices where there is a 300ms delay by default. So this means that the callback is fired to hide the container then the toggle functions runs after 300ms, reopening the container. On desktop, with a mouse click, the toggle function fires first and then the callback
// Angular App Code
var app = angular.module('myApp', ['offClick']);
app.controller('myAppController', ['$scope', '$timeout', function($scope,$timeout) {
$scope.showContainer = false;
$scope.toggleContainer = function() {
$timeout(function() {
$scope.showContainer = !$scope.showContainer;
}, 300);
};
$scope.hideContainer = function(scope, event, p) {
$scope.showContainer = false;
console.log('event: ', event);
console.log('scope: ', scope);
console.log(p);
};
}]);
// Off Click Directive Code
angular.module('offClick', [])
.directive('offClick', ['$rootScope', '$parse', function ($rootScope, $parse) {
var id = 0;
var listeners = {};
// add variable to detect touch users moving..
var touchMove = false;
// Add event listeners to handle various events. Destop will ignore touch events
document.addEventListener("touchmove", offClickEventHandler, true);
document.addEventListener("touchend", offClickEventHandler, true);
document.addEventListener('click', offClickEventHandler, true);
function targetInFilter(target, elms) {
if (!target || !elms) return false;
var elmsLen = elms.length;
for (var i = 0; i < elmsLen; ++i) {
var currentElem = elms[i];
var containsTarget = false;
try {
containsTarget = currentElem.contains(target);
} catch (e) {
// If the node is not an Element (e.g., an SVGElement) node.contains() throws Exception in IE,
// see https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
// In this case we use compareDocumentPosition() instead.
if (typeof currentElem.compareDocumentPosition !== 'undefined') {
containsTarget = currentElem === target || Boolean(currentElem.compareDocumentPosition(target) & 16);
}
}
if (containsTarget) {
return true;
}
}
return false;
}
function offClickEventHandler(event) {
// If event is a touchmove adjust touchMove state
if( event.type === 'touchmove' ){
touchMove = true;
// And end function
return false;
}
// This will always fire on the touchend after the touchmove runs...
if( touchMove ){
// Reset touchmove to false
touchMove = false;
// And end function
return false;
}
var target = event.target || event.srcElement;
angular.forEach(listeners, function (listener, i) {
if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
$rootScope.$evalAsync(function () {
listener.cb(listener.scope, {
$event: event
});
});
}
});
}
return {
restrict: 'A',
compile: function ($element, attr) {
var fn = $parse(attr.offClick);
return function (scope, element) {
var elmId = id++;
var offClickFilter;
var removeWatcher;
offClickFilter = document.querySelectorAll(scope.$eval(attr.offClickFilter));
if (attr.offClickIf) {
removeWatcher = $rootScope.$watch(function () {
return $parse(attr.offClickIf)(scope);
}, function (newVal) {
if (newVal) {
on();
} else if (!newVal) {
off();
}
});
} else {
on();
}
attr.$observe('offClickFilter', function (value) {
offClickFilter = document.querySelectorAll(scope.$eval(value));
});
scope.$on('$destroy', function () {
off();
if (removeWatcher) {
removeWatcher();
}
element = null;
});
function on() {
listeners[elmId] = {
elm: element[0],
cb: fn,
scope: scope,
offClickFilter: offClickFilter
};
}
function off() {
listeners[elmId] = null;
delete listeners[elmId];
}
};
}
};
}]);
/* Styles go here */
.container {
background: blue;
color: #fff;
height: 300px;
width: 300px;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://code.angularjs.org/1.4.0/angular.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body data-ng-app="myApp">
<h1>Hello Plunker!</h1>
<div data-ng-controller="myAppController">
<button data-ng-click="toggleContainer()">Toggle Container</button>
<div class="container" data-ng-show="showContainer" data-off-click="hideContainer()" data-off-click-if="showContainer">
This is the container
</div>
</div>
</body>
</html>
http://jsbin.com/hibovu
The problem is that when you click on the button the both of the functions are firing:
hideContainer from the directive.
toggleContainer from the click event (that showing the div again).
The solution
Add event.stopPropagation(); before you evaluate the hide callback.
How do you do this?
Pass the event to the function data-off-click="hideContainer($event)".
Add the $event param in the definition of the hideContainer function in the $scope like this: $scope.hideContainer = function($event)
And the full code:
// Angular App Code
var app = angular.module('myApp', ['offClick']);
app.controller('myAppController', ['$scope', '$timeout', function($scope,$timeout) {
$scope.showContainer = false;
$scope.toggleContainer = function() {
$timeout(function() {
$scope.showContainer = !$scope.showContainer;
}, 300);
};
$scope.hideContainer = function($event) {
$event.stopPropagation();
$timeout(function(){
$scope.showContainer = false;
});
};
}]);
// Off Click Directive Code
angular.module('offClick', [])
.directive('offClick', ['$rootScope', '$parse', function ($rootScope, $parse) {
var id = 0;
var listeners = {};
// add variable to detect touch users moving..
var touchMove = false;
// Add event listeners to handle various events. Destop will ignore touch events
document.addEventListener("touchmove", offClickEventHandler, true);
document.addEventListener("touchend", offClickEventHandler, true);
document.addEventListener('click', offClickEventHandler, true);
function targetInFilter(target, elms) {
if (!target || !elms) return false;
var elmsLen = elms.length;
for (var i = 0; i < elmsLen; ++i) {
var currentElem = elms[i];
var containsTarget = false;
try {
containsTarget = currentElem.contains(target);
} catch (e) {
// If the node is not an Element (e.g., an SVGElement) node.contains() throws Exception in IE,
// see https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
// In this case we use compareDocumentPosition() instead.
if (typeof currentElem.compareDocumentPosition !== 'undefined') {
containsTarget = currentElem === target || Boolean(currentElem.compareDocumentPosition(target) & 16);
}
}
if (containsTarget) {
return true;
}
}
return false;
}
function offClickEventHandler(event) {
// If event is a touchmove adjust touchMove state
if( event.type === 'touchmove' ){
touchMove = true;
// And end function
return false;
}
// This will always fire on the touchend after the touchmove runs...
if( touchMove ){
// Reset touchmove to false
touchMove = false;
// And end function
return false;
}
var target = event.target || event.srcElement;
angular.forEach(listeners, function (listener, i) {
if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
//$rootScope.$evalAsync(function () {
listener.cb(listener.scope, {
$event: event
});
//});
}
});
}
return {
restrict: 'A',
compile: function ($element, attr) {
var fn = $parse(attr.offClick);
return function (scope, element) {
var elmId = id++;
var offClickFilter;
var removeWatcher;
offClickFilter = document.querySelectorAll(scope.$eval(attr.offClickFilter));
if (attr.offClickIf) {
removeWatcher = $rootScope.$watch(function () {
return $parse(attr.offClickIf)(scope);
}, function (newVal) {
if (newVal) {
on();
} else if (!newVal) {
off();
}
});
} else {
on();
}
attr.$observe('offClickFilter', function (value) {
offClickFilter = document.querySelectorAll(scope.$eval(value));
});
scope.$on('$destroy', function () {
off();
if (removeWatcher) {
removeWatcher();
}
element = null;
});
function on() {
listeners[elmId] = {
elm: element[0],
cb: fn,
scope: scope,
offClickFilter: offClickFilter
};
}
function off() {
listeners[elmId] = null;
delete listeners[elmId];
}
};
}
};
}]);
.container {
background: blue;
color: #fff;
height: 300px;
width: 300px;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body data-ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<h1>Hello Plunker!</h1>
<div data-ng-controller="myAppController">
<button data-ng-click="toggleContainer()">Toggle Container</button>
<div class="container" data-ng-show="showContainer" data-off-click="hideContainer($event)" data-off-click-if="showContainer">
This is the container
</div>
</div>
</body>
</html>
http://jsbin.com/hibovu/3/edit?html,css,js
I'm having a hard time to get promises to work with the right this scope inside prototypes.
Here is my code:
'use strict';
angular.module('testApp').factory('UrlSearchApi',
function($resource, URL_SEARCH_API, PAGE_SIZE, $q){
var resource = $resource(URL_SEARCH_API);
resource.Scroll = function () {
return this.reset();
};
resource.Scroll.prototype.reset = function () {
this.visibleItems = [];
this.allItems = [];
this.busy = null;
return this;
};
resource.Scroll.prototype.fetch = function(query){
var params = {};
if(query) { params.q = query; }
return resource.query(params).$promise;
};
resource.Scroll.prototype.loadAllItems = function (results) {
var d = $q.defer();
angular.forEach(results, function (result, i) {
this.allItems.push(result);
if(i === results.length - 1 ) { d.resolve(); }
}, this);
return d.promise;
};
resource.Scroll.prototype.loadVisibleItems = function () {
var length = this.visibleItems.length,
offset = parseInt(length / PAGE_SIZE),
start = PAGE_SIZE * offset,
end = start + PAGE_SIZE,
subset = this.allItems.slice(start, end),
d = $q.defer();
angular.forEach(subset, function (item, i) {
this.visibleItems.push(item);
if(i === subset.length - 1 ) { d.resolve(); }
}, this);
return d.promise;
};
resource.Scroll.prototype.nextPage = function (query) {
if(this.busy) { return; }
console.log('nextPage ', query);
var tasks = [],
that = this;
if(!this.allItems.length) {
this.reset();
this.busy = true;
return this.fetch(query)
.then(this.loadAllItems)
.then(this.loadVisibleItems)
.finally(function () {
this.busy = false;
});
} else {
this.busy = true;
return this.loadVisibleItems().finally(function () {
this.busy = false;
});
}
};
return resource;
});
Whenever I run the tests I get
describe('#nextPage', function () {
var scroll;
describe('when there is NO search term (show all)', function () {
beforeEach(function (done) {
scroll = new UrlSearchApi.Scroll();
$httpBackend.expectGET('/policy/search')
.respond(200, arrayGenerator(123));
scroll.nextPage().then(done);
$httpBackend.flush();
$rootScope.$apply();
});
it('should load all the items in all items variable', function () {
expect(scroll.allItems.length).toBe(123);
});
});
});
I get the following error:
TypeError: 'undefined' is not an object (evaluating 'this.allItems')
I now that $q in strict mode sets the this inside then to undefined. I tried using bind(this) in multiple places but not luck... Any ideas?
I've already answered a question like this here.
Just let me know in comments if you still have questions.
Upd. Try to update your resource.Scroll.prototype.nextPage method like this:
if(!this.allItems.length) {
this.reset();
this.busy = true;
return this.fetch(query)
.then(this.loadAllItems.bind(this)) //bind here
.then(this.loadVisibleItems.bind(this)) // here
.finally(function () {
this.busy = false;
}.bind(this)); //and here
But keep in mind - when you pass a function as a callback to a then or to forEach e.t.c it'll lose this context. So, use bind exactly when you pass the function which uses this syntax as a callback.
I am new to angularjs the below directive is to support decimal and commas, it works fine when a change is made to the field how ever the data in the fields are not validated when the page loads
var app = angular.module('myApply.directives', [], function () {
});
app.directive('numericDecimalInput', function($filter, $browser, $locale,$rootScope) {
return {
require: 'ngModel',
priority: 1,
link: function($scope, $element, $attrs, ngModelCtrl) {
var replaceRegex = new RegExp($locale.NUMBER_FORMATS.GROUP_SEP, 'g');
var fraction = $attrs.fraction || 0;
var listener = function() {
var value = $element.val().replace(replaceRegex, '');
$element.val($filter('number')(value, fraction));
};
var validator=function(viewValue) {
ngModelCtrl.$setValidity('outOfMax', true);
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
ngModelCtrl.$setValidity('rangeValid', true);
if(!_.isUndefined(viewValue))
{
var newVal = viewValue.replace(replaceRegex, '');
var newValAsNumber = newVal * 1;
// check if new value is numeric, and set control validity
if (isNaN(newValAsNumber)) {
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', false);
} else
{
if (newVal < 0) {
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', false);
}
else {
newVal = newValAsNumber.toFixed(fraction);
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', true);
if (!(_.isNull($attrs.maxamt) || _.isUndefined($attrs.maxamt))) {
var maxAmtValue = Number($attrs.maxamt) || Number($scope.$eval($attrs.maxamt));
if (newVal > maxAmtValue) {
ngModelCtrl.$setValidity('outOfMax', false);
} else {
ngModelCtrl.$setValidity('outOfMax', true);
}
if(!(_.isNull($attrs.minamt) || _.isUndefined($attrs.minamt)))
{
var minAmtValue = Number($attrs.minamt)|| Number($scope.$eval($attrs.minamt));
if((newVal > maxAmtValue) || (newVal < minAmtValue)){
ngModelCtrl.$setValidity('rangeValid', false);
}
else
{
ngModelCtrl.$setValidity('rangeValid', true);
}
}
}
else if((!(_.isNull($attrs.minamt) || _.isUndefined($attrs.minamt))))
{
var minAmtValue = Number($attrs.minamt)|| Number($scope.$eval($attrs.minamt));
if(newVal < minAmtValue)
{
ngModelCtrl.$setValidity('outOfMin', false);
}
else
{
ngModelCtrl.$setValidity('outOfMin', true);
}
}
else {
ngModelCtrl.$setValidity('outOfMax', true);
}
}
}
return newVal;
}
};
// This runs when the model gets updated on the scope directly and keeps our view in sync
ngModelCtrl.$render = function() {
ngModelCtrl.$setValidity('outOfMax', true);
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
$element.val($filter('number')(ngModelCtrl.$viewValue, fraction));
};
$element.bind('change', listener);
$element.bind('keydown', function(event) {
var key = event.keyCode;
// If the keys include the CTRL, SHIFT, ALT, or META keys, home, end, or the arrow keys, do nothing.
// This lets us support copy and paste too
if (key == 91 || (15 < key && key < 19) || (35 <= key && key <= 40))
return;
});
$element.bind('paste cut', function() {
$browser.defer(listener);
});
ngModelCtrl.$parsers.push(validator);
ngModelCtrl.$formatters.push(validator);
}
};
});
Could some one please let me know as what I am missing .
Thanking you in advance.
Have you declared your app (doesn't show this in your code)? ie:
var app = angular.module('app', []);
Do you have ng-app in your HTML document anywhere?
<html lang="en-GB" ng-app="app">
Check out the documentation to get started with modules (Angular is very well documented, well worth reading): https://docs.angularjs.org/guide/module
app.directive('numericDecimalInput', function($filter, $browser, $locale,$rootScope) {
return {
require: 'ngModel',
priority: 1,
link: function($scope, $element, $attrs, ngModelCtrl) {
...
validator($element.val());
}
};
});
So I've been following this tutorial here and it shows you how to validate dates compared with each other. I'm getting an error in the first block of code which I've commented and it says "Unable to get property 'element' of undefined or null reference" which originates from this line of code customValidation.formValidator = $(event.data.source).closest('form').data('validator') does anyone know of a work around for this so I don't get an error. I'm using the latest unobtrusive validation
window.customValidation = window.customValidation ||
{
relatedControlValidationCalled: function (event) {
if (!customValidation.activeValidator) {
customValidation.formValidator = $(event.data.source).closest('form').data('validator');
}
// code error below
customValidation.formValidator.element($(event.data.target));
},
relatedControlCollection: [],
formValidator: undefined,
addDependatControlValidaitonHandler: function (element, dependentPropertyName) {
var id = $(element).attr('id');
if ($.inArray(id, customValidation.relatedControlCollection) < 0) {
customValidation.relatedControlCollection.push(id);
$(element).on(
'blur',
{ source: $(element), target: $('#' + dependentPropertyName) },
customValidation.relatedControlValidationCalled);
}
}
};
adapter:
$.validator.unobtrusive.adapters.add('comparedates', ['otherpropertyname', 'allowequality'],
function (options) {
options.rules['comparedates'] = options.params;
if (options.message) {
options.messages['comparedates'] = options.message;
}
}
);
validator method:
$.validator.addMethod('comparedates', function (value, element, params) {
var otherFieldValue = $('input[name="' + params.otherpropertyname + '"]').val();
if (otherFieldValue && value) {
var currentValue = Date.parse(value);
var otherValue = Date.parse(otherFieldValue);
if ($(element).attr('name').toLowerCase().indexOf('begin') >= 0) {
if (params.allowequality) {
if (currentValue > otherValue) {
return false;
}
} else {
if (currentValue >= otherValue) {
return false;
}
}
} else {
if (params.allowequality) {
if (currentValue < otherValue) {
return false;
}
} else {
if (currentValue <= otherValue) {
return false;
}
}
}
}
customValidation.addDependatControlValidaitonHandler(element, params.otherpropertyname);
return true;
}, '');
Maybe you are loading this code too early, before the form is in the DOM. Make sure your code is protected by $(document).ready(your code here);