diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..37c5a1a --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ +Hi, thanks for contributing! + +This project is maintained in my spare time, so in order to help me address your issue as quickly as +possible, please provide as much of the following information as you can: + +- Title: Please indicate the module in question, e.g. "dirPagination: X is broken when Y" +- If reporting on dirPagination, please include the version you are using (can be found in the package.json / bower.json file) +- If you are able to reproduce your issue on Plunker, this will vastly increase the chances of a rapid response from me. + +-- Michael + +======= +(Delete the above. Fill in the rest as applicable) + +**Description of issue**: + +**Steps to reproduce**: + +**Expected result**: + +**Actual result**: + +**Demo**: (for dirPagination, fork and modify this Plunk: http://plnkr.co/edit/b37IdFFJUokaeSummETX?p=preview) + +Any relevant code: +``` + +``` diff --git a/README.md b/README.md index b8338d9..ac0d61e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Angular Utilities +### No longer maintained +(20/04/2017) - I am no longer actively maintaining this project. I no longer use AngularJS in my own projects and do not have the time to dedicate to maintiaining this project as well as my other active open source projects. Thank you for your understanding. + +--- + I am working on a large-scale AngularJS-based project and I'll be extracting any useful re-usable components that I make and putting them here. @@ -33,4 +38,4 @@ This code is made available under the MIT license, so feel free to use any of th ## License -MIT \ No newline at end of file +MIT diff --git a/src/directives/disqus/README.md b/src/directives/disqus/README.md index 2072170..e386a83 100644 --- a/src/directives/disqus/README.md +++ b/src/directives/disqus/README.md @@ -1,5 +1,11 @@ # Disqus Directive +### No longer maintained +(20/04/2017) - I am no longer actively maintaining this project. I no longer use AngularJS in my own projects and do not have the time to dedicate to maintiaining this project as well as my other active open source projects. Thank you for your understanding. + +--- + + A directive to embed a Disqus comments widget on your AngularJS page. ## Prerequisites @@ -15,7 +21,9 @@ the default hash-only (no `!`) urls that Angular uses. ## Installation -1. Download the file `dirDisqus.js` or use the Bower command `bower install angular-utils-disqus` +1. Download the file `dirDisqus.js` or: + * via Bower: `bower install angular-utils-disqus` + * via npm: `npm install angular-utils-disqus` 2. Include the JavaScript file in your index.html page. 2. Add a reference to the module `angularUtils.directives.dirDisqus` to your app. @@ -25,16 +33,23 @@ First, put the directive code in your app, wherever you store your directives. Wherever you want the Disqus comments to appear, add the following to your template: -```html - - +``` + +``` + +And in your controller: + +``` +$scope.disqusConfig = { + disqus_shortname: 'Your disqus shortname', + disqus_identifier: 'Comments identifier', + disqus_url: 'Comments url' +}; ``` The attributes given above are all required. The inclusion of the identifier and URL ensure that identifier conflicts will not occur. See http://help.disqus.com/customer/portal/articles/662547-why-are-the-same-comments-showing-up-on-multiple-pages- -If the identifier and URL and not included as attributes, the directive will throw an exception. +If the identifier and URL and not included as attributes, the directive will not appear. ## Full API @@ -42,20 +57,19 @@ You can optionally specify the other configuration variables by including the as on the directive's element tag. For more information on the available config vars, see the [Disqus docs](http://help.disqus.com/customer/portal/articles/472098-javascript-configuration-variables). -```HTML - - +``` +$scope.disqusConfig = { + disqus_shortname: 'Your disqus shortname', + disqus_identifier: 'Comments identifier', + disqus_url: 'Comments url', + disqus_title: 'Comments title', + disqus_category_id: 'Comments category id }}', + disqus_disable_mobile: 'false', + disqus_config_language: 'Comments language', + disqus_remote_auth_s3: 'remote_auth_s3', + disqus_api_key: 'public_api_key', + disqus_on_ready: ready() +}; ``` If using the `disqus-config-language` setting, please see [this Disqus article on multi-lingual websites](https://help.disqus.com/customer/portal/articles/466249-multi-lingual-websites) @@ -65,42 +79,8 @@ for which languages are supported. If using the `disqus-remote-auth-s3 and disqus-api-key` setting, please see [Integrating Single Sign-On](https://help.disqus.com/customer/portal/articles/236206#sso-script) to know how to generate a remote_auth_s3 and public_api_key. -note:Single Sign-on (SSO) allows users to sign into a site and be able to use Disqus Comments without having to re-authenticate Disqus. SSO will create a site-specific user profile on Disqus, in a way that will prevent conflict with existing Disqus users. - - -## `ready-to-bind` attribute - -If you are loading the page asynchronously, the model data (`$scope.article` in the above example) used to populate the config variables above -will probably not be defined at the time the page is loaded. This will result in your config settings -being all undefined. To get around this, you can specify a scope variable that should be set to (or evaluate to) `false` -until your data is loaded, at which point you can set it to `true`. The directive watches this property and once it changes -to `true`, any config attributes which are bound to your model should be available and used to load up the Disqus widget. - -For example: - -```JavaScript -// simple example of controller loading async data -function myController($scope, $http) { - $scope.contentLoaded = false; - - $http.get('api/article/1').then(function(result) { - $scope.article = result.article; - $scope.contentLoaded = true; // this tells the directive that it should load the Disqus widget now - }) -} -``` -```html -// in your view code - - -``` - -If you omit the `ready-to-bind` attribute, the Disqus widget will be created immediately. This is okay so long as you don't rely on interpolated data which is not available on page load. +Note: Single Sign-on (SSO) allows users to sign into a site and be able to use Disqus Comments without having to re-authenticate Disqus. SSO will create a site-specific user profile on Disqus, in a way that will prevent conflict with existing Disqus users. ## `disqus-on-ready` attribute - If Disqus is rendered, `disqus-on-ready` function will be called. Callback is registered to disqus by similar technique - as explained in [this post](https://help.disqus.com/customer/portal/articles/466258-capturing-disqus-commenting-activity-via-callbacks). +If Disqus is rendered, `disqus-on-ready` function will be called. Callback is registered to disqus by similar technique as explained in [this post](https://help.disqus.com/customer/portal/articles/466258-capturing-disqus-commenting-activity-via-callbacks). diff --git a/src/directives/disqus/dirDisqus.js b/src/directives/disqus/dirDisqus.js index a122762..ff0f182 100644 --- a/src/directives/disqus/dirDisqus.js +++ b/src/directives/disqus/dirDisqus.js @@ -1,12 +1,13 @@ -/** +/** * A directive to embed a Disqus comments widget on your AngularJS page. * * Created by Michael on 22/01/14. + * Modified by Serkan "coni2k" Holat on 24/02/16. * Copyright Michael Bromley 2014 * Available under the MIT license. */ -(function() { +(function () { /** * Config @@ -19,84 +20,69 @@ var module; try { module = angular.module(moduleName); - } catch(err) { + } catch (err) { // named module does not exist, so create one module = angular.module(moduleName, []); } - module.directive('dirDisqus', ['$window', function($window) { + module.directive('dirDisqus', ['$window', function ($window) { return { restrict: 'E', scope: { - disqus_shortname: '@disqusShortname', - disqus_identifier: '@disqusIdentifier', - disqus_title: '@disqusTitle', - disqus_url: '@disqusUrl', - disqus_category_id: '@disqusCategoryId', - disqus_disable_mobile: '@disqusDisableMobile', - disqus_config_language : '@disqusConfigLanguage', - disqus_remote_auth_s3 : '@disqusRemoteAuthS3', - disqus_api_key : '@disqusApiKey', - disqus_on_ready: "&disqusOnReady", - readyToBind: "@" + config: '=' }, - template: '
comments powered by Disqus', - link: function(scope) { + template: '
', + link: function (scope) { - // ensure that the disqus_identifier and disqus_url are both set, otherwise we will run in to identifier conflicts when using URLs with "#" in them - // see http://help.disqus.com/customer/portal/articles/662547-why-are-the-same-comments-showing-up-on-multiple-pages- - if (typeof scope.disqus_identifier === 'undefined' || typeof scope.disqus_url === 'undefined') { - throw "Please ensure that the `disqus-identifier` and `disqus-url` attributes are both set."; - } + scope.$watch('config', configChanged, true); - scope.$watch("readyToBind", function(isReady) { + function configChanged() { - // If the directive has been called without the 'ready-to-bind' attribute, we - // set the default to "true" so that Disqus will be loaded straight away. - if ( !angular.isDefined( isReady ) ) { - isReady = "true"; + // Ensure that the disqus_identifier and disqus_url are both set, otherwise we will run in to identifier conflicts when using URLs with "#" in them + // see http://help.disqus.com/customer/portal/articles/662547-why-are-the-same-comments-showing-up-on-multiple-pages- + if (!scope.config.disqus_shortname || + !scope.config.disqus_identifier || + !scope.config.disqus_url) { + return; } - if (scope.$eval(isReady)) { - console.log('remote'+scope.disqus_remote_auth_s3); - // put the config variables into separate global vars so that the Disqus script can see them - $window.disqus_shortname = scope.disqus_shortname; - $window.disqus_identifier = scope.disqus_identifier; - $window.disqus_title = scope.disqus_title; - $window.disqus_url = scope.disqus_url; - $window.disqus_category_id = scope.disqus_category_id; - $window.disqus_disable_mobile = scope.disqus_disable_mobile; - $window.disqus_config = function () { - this.language = scope.disqus_config_language; - this.page.remote_auth_s3 = scope.disqus_remote_auth_s3; - this.page.api_key = scope.disqus_api_key; - if (scope.disqus_on_ready) { - this.callbacks.onReady = [function () { - scope.disqus_on_ready(); - }]; - } - }; - // get the remote Disqus script and insert it into the DOM, but only if it not already loaded (as that will cause warnings) - if (!$window.DISQUS) { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + scope.disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - } else { - $window.DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = scope.disqus_identifier; - this.page.url = scope.disqus_url; - this.page.title = scope.disqus_title; - this.language = scope.disqus_config_language; - this.page.remote_auth_s3=scope.disqus_remote_auth_s3; - this.page.api_key=scope.disqus_api_key; - } - }); + + $window.disqus_shortname = scope.config.disqus_shortname; + $window.disqus_identifier = scope.config.disqus_identifier; + $window.disqus_url = scope.config.disqus_url; + $window.disqus_title = scope.config.disqus_title; + $window.disqus_category_id = scope.config.disqus_category_id; + $window.disqus_disable_mobile = scope.config.disqus_disable_mobile; + $window.disqus_config = function () { + this.language = scope.config.disqus_config_language; + this.page.remote_auth_s3 = scope.config.disqus_remote_auth_s3; + this.page.api_key = scope.config.disqus_api_key; + if (scope.config.disqus_on_ready) { + this.callbacks.onReady = [function () { + scope.config.disqus_on_ready(); + }]; } + }; + + // Get the remote Disqus script and insert it into the DOM, but only if it not already loaded (as that will cause warnings) + if (!$window.DISQUS) { + var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; + dsq.src = '//' + scope.config.disqus_shortname + '.disqus.com/embed.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); + } else { + $window.DISQUS.reset({ + reload: true, + config: function () { + this.page.identifier = scope.config.disqus_identifier; + this.page.url = scope.config.disqus_url; + this.page.title = scope.config.disqus_title; + this.language = scope.config.disqus_config_language; + this.page.remote_auth_s3 = scope.config.disqus_remote_auth_s3; + this.page.api_key = scope.config.disqus_api_key; + } + }); } - }); + } } }; }]); - -})(); \ No newline at end of file +})(); diff --git a/src/directives/disqus/dirDisqus.spec.js b/src/directives/disqus/dirDisqus.spec.js index ef57908..b6ce512 100644 --- a/src/directives/disqus/dirDisqus.spec.js +++ b/src/directives/disqus/dirDisqus.spec.js @@ -3,7 +3,7 @@ * them on their own using `ddescribe` works okay. Therefore this test is ignored in general unless specifically testing * this directive, in which case change `xdescribe` to `ddescribe`. */ -describe('dirDisqus directive', function() { +xdescribe('dirDisqus directive', function() { var scope, elem, compiled, diff --git a/src/directives/pagination/README.md b/src/directives/pagination/README.md index e3caba2..4fd209f 100644 --- a/src/directives/pagination/README.md +++ b/src/directives/pagination/README.md @@ -1,11 +1,18 @@ # Pagination Directive +### No longer maintained +(20/04/2017) - I am no longer actively maintaining this project. I no longer use AngularJS in my own projects and do not have the time to dedicate to maintiaining this project as well as my other active open source projects. Thank you for your understanding. + +--- + ## Another one? Yes, there are quite a few pagination solutions for Angular out there already, but what I wanted to do was make something that would be truly plug-n-play - no need to do any set-up or logic in your controller. Just add an attribute, drop in your navigation wherever you like, and boom - instant, full-featured pagination. +(**Looking for the Angular2 version? [Right here!](https://github.com/michaelbromley/ng2-pagination)**) + ## Demo [Here is a working demo on Plunker](http://plnkr.co/edit/Wtkv71LIqUR4OhzhgpqL?p=preview) which demonstrates some cool features such as live-binding the "itemsPerPage" and @@ -30,6 +37,7 @@ filtering of the collection. - [Styling](#styling) - [FAQ](#frequently-asked-questions) - [Contribution](#contribution) +- [Changelog](#changelog) - [Credits](#credits) ## Basic Example @@ -108,9 +116,9 @@ expects pagination lists to have a particular structure different from the defau If you plan to use a custom template, take a look at the default as demonstrated in dirPagination.tpl.html to get an idea of how it interacts with the directive. -There are two ways to specify the template of the pagination controls directive: +There are three ways to specify the template of the pagination controls directive: -**1. Use the `paginationTemplateProvider` in your app's config block to set a global template for your app:** +**1. Use the `paginationTemplateProvider` in your app's config block to set a global templateUrl for your app:** ```JavaScript myApp.config(function(paginationTemplateProvider) { @@ -118,12 +126,33 @@ myApp.config(function(paginationTemplateProvider) { }); ``` -**2. Use the `template-url` attribute on each pagination controls directive:** +**2. Use the `paginationTemplateProvider` in your app's config block to set a global template string for your app:** + +```JavaScript +myApp.config(function(paginationTemplateProvider) { + paginationTemplateProvider.setString(''); + + // or with e.g. Webpack you might do + paginationTemplateProvider.setString(require('/path/to/customPagination.tpl.html')); +}); +``` + +**3. Use the `template-url` attribute on each pagination controls directive:** ```HTML ``` +#### Template Priority + +If you use more than one method for specifying the template, the actual template to use will be decided based on +the following order of precedence (highest priority first): + +1. `paginationTemplate.getString()` +2. `template-url` +3. `paginationTemplate.getPath()` +4. (default built-in template) + ## Directives API The following attributes form the API for the pagination and pagination-controls directives. Optional attributes are marked as such, @@ -141,7 +170,7 @@ The optional third argument `paginationId` is used when you need more than one i on setting up multiple instances. * **`current-page`** (optional) Specify a property on your controller's $scope that will be bound to the current -page of the pagination. If this is not specified, the directive will automatically create a property named `__currentPage` and use +page of the pagination. If this is not specified, the directive will automatically create a property named `__default__currentPage` and use that instead. * **`pagination-id`** (optional) Used to group together the dir-paginate directive with a corresponding dir-pagination-controls when you need more than @@ -164,8 +193,8 @@ pagination. pagination. * **`on-page-change`** (optional, default = null) Specify a callback method to run each time one of the pagination links is clicked. The method will be passed the -argument `newPageNumber`, which is an integer equal to the page number that has just been navigated to. **Note** you must use that exact argument name in your view, -i.e. ``, and the method you specify must be defined on your controller $scope. +optional arguments `newPageNumber` and `oldPageNumber`, which are integers equal to the page number that has just been navigated to, and the one just left, respectively. **Note** you must use that exact argument name in your view, +i.e. ``, and the method you specify must be defined on your controller $scope. * **`pagination-id`** (optional) Used to group together the dir-pagination-controls with a corresponding dir-paginate when you need more than one pagination instance per page. See the section below on setting up multiple instances. @@ -391,6 +420,11 @@ Karma is set up if you run `grunt watch` as you make changes. At a minimum, make sure that all the tests still pass. Thanks! +## Changelog + +Please see the [releases page of the package repo](https://github.com/michaelbromley/angularUtils-pagination/releases) for details +of each released version. + ## Credits I did quite a bit of research before I figured I needed to make my own directive, and I picked up a lot of good ideas @@ -406,3 +440,7 @@ from their pagination directive. from the various contributors to this thread. * Massive credit is due to all the [contributors](https://github.com/michaelbromley/angularUtils/graphs/contributors) to this project - they have brought improvements that I would not have the time or insight to figure out myself. + +## License + +MIT diff --git a/src/directives/pagination/dirPagination.js b/src/directives/pagination/dirPagination.js index 99fb7cf..d5103ff 100644 --- a/src/directives/pagination/dirPagination.js +++ b/src/directives/pagination/dirPagination.js @@ -68,6 +68,12 @@ // Now that we have access to the `scope` we can interpolate any expression given in the paginationId attribute and // potentially register a new ID if it evaluates to a different value than the rawId. var paginationId = $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID; + + // (TODO: this seems sound, but I'm reverting as many bug reports followed it's introduction in 0.11.0. + // Needs more investigation.) + // In case rawId != paginationId we deregister using rawId for the sake of general cleanliness + // before registering using paginationId + // paginationService.deregisterInstance(rawId); paginationService.registerInstance(paginationId); var repeatExpression = getRepeatExpression(expression, paginationId); @@ -89,17 +95,27 @@ } }); } else { + paginationService.setAsyncModeFalse(paginationId); scope.$watchCollection(function() { return collectionGetter(scope); }, function(collection) { if (collection) { - paginationService.setCollectionLength(paginationId, collection.length); + var collectionLength = (collection instanceof Array) ? collection.length : Object.keys(collection).length; + paginationService.setCollectionLength(paginationId, collectionLength); } }); } // Delegate to the link function returned by the new compilation of the ng-repeat compiled(scope); + + // (TODO: Reverting this due to many bug reports in v 0.11.0. Needs investigation as the + // principle is sound) + // When the scope is destroyed, we make sure to remove the reference to it in paginationService + // so that it can be properly garbage collected + // scope.$on('$destroy', function destroyDirPagination() { + // paginationService.deregisterInstance(paginationId); + // }); }; } @@ -185,7 +201,9 @@ // Replace any non-alphanumeric characters which might confuse // the $parse service and give unexpected results. // See https://github.com/michaelbromley/angularUtils/issues/233 - var defaultCurrentPage = (paginationId + '__currentPage').replace(/\W/g, '_'); + // Adding the '_' as a prefix resolves an issue where paginationId might be have a digit as its first char + // See https://github.com/michaelbromley/angularUtils/issues/400 + var defaultCurrentPage = '_' + (paginationId + '__currentPage').replace(/\W/g, '_'); scope[defaultCurrentPage] = 1; currentPageGetter = $parse(defaultCurrentPage); } @@ -213,11 +231,8 @@ var numberRegex = /^\d+$/; - return { + var DDO = { restrict: 'AE', - templateUrl: function(elem, attrs) { - return attrs.templateUrl || paginationTemplate.getPath(); - }, scope: { maxSize: '=?', onPageChange: '&?', @@ -227,6 +242,23 @@ link: dirPaginationControlsLinkFn }; + // We need to check the paginationTemplate service to see whether a template path or + // string has been specified, and add the `template` or `templateUrl` property to + // the DDO as appropriate. The order of priority to decide which template to use is + // (highest priority first): + // 1. paginationTemplate.getString() + // 2. attrs.templateUrl + // 3. paginationTemplate.getPath() + var templateString = paginationTemplate.getString(); + if (templateString !== undefined) { + DDO.template = templateString; + } else { + DDO.templateUrl = function(elem, attrs) { + return attrs.templateUrl || paginationTemplate.getPath(); + }; + } + return DDO; + function dirPaginationControlsLinkFn(scope, element, attrs) { // rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has @@ -237,7 +269,9 @@ if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) { var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' '; - console.warn('Pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive, which was not found at link time.'); + if (window.console) { + console.warn('Pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive, which was not found at link time.'); + } } if (!scope.maxSize) { scope.maxSize = 9; } @@ -257,6 +291,13 @@ total: 1 }; + scope.$watch('maxSize', function(val) { + if (val) { + paginationRange = Math.max(scope.maxSize, 5); + generatePagination(); + } + }); + scope.$watch(function() { if (paginationService.isRegistered(paginationId)) { return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId); @@ -308,13 +349,19 @@ function goToPage(num) { if (paginationService.isRegistered(paginationId) && isValidPageNumber(num)) { + var oldPageNumber = scope.pagination.current; + scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); scope.pagination.current = num; updateRangeValues(); - // if a callback has been set, then call it with the page number as an argument + // if a callback has been set, then call it with the page number as the first argument + // and the previous page number as a second argument if (scope.onPageChange) { - scope.onPageChange({ newPageNumber : num }); + scope.onPageChange({ + newPageNumber : num, + oldPageNumber : oldPageNumber + }); } } } @@ -503,6 +550,10 @@ } }; + this.deregisterInstance = function(instanceId) { + delete instances[instanceId]; + }; + this.isRegistered = function(instanceId) { return (typeof instances[instanceId] !== 'undefined'); }; @@ -541,6 +592,10 @@ instances[instanceId].asyncMode = true; }; + this.setAsyncModeFalse = function(instanceId) { + instances[instanceId].asyncMode = false; + }; + this.isAsyncMode = function(instanceId) { return instances[instanceId].asyncMode; }; @@ -552,15 +607,33 @@ function paginationTemplateProvider() { var templatePath = 'angularUtils.directives.dirPagination.template'; + var templateString; + /** + * Set a templateUrl to be used by all instances of + * @param {String} path + */ this.setPath = function(path) { templatePath = path; }; + /** + * Set a string of HTML to be used as a template by all instances + * of . If both a path *and* a string have been set, + * the string takes precedence. + * @param {String} str + */ + this.setString = function(str) { + templateString = str; + }; + this.$get = function() { return { getPath: function() { return templatePath; + }, + getString: function() { + return templateString; } }; }; diff --git a/src/directives/pagination/dirPagination.spec.js b/src/directives/pagination/dirPagination.spec.js index 414e0c0..07a7e45 100644 --- a/src/directives/pagination/dirPagination.spec.js +++ b/src/directives/pagination/dirPagination.spec.js @@ -14,6 +14,13 @@ describe('dirPagination directive', function() { beforeEach(module('angularUtils.directives.dirPagination')); beforeEach(module('templates-main')); + // used to test the paginationTemplateProvider (see end of file) + var templateProvider; + angular.module('customTemplateTestApp', []); + beforeEach(module('customTemplateTestApp', function(paginationTemplateProvider) { + templateProvider = paginationTemplateProvider; + })); + beforeEach(inject(function($rootScope, _$compile_, _$timeout_) { $compile = _$compile_; @@ -216,6 +223,13 @@ describe('dirPagination directive', function() { expect(getListItems()).toEqual(['item 16', 'item 17', 'item 18', 'item 19', 'item 20']); }); + it('should display the correct pagination links', function() { + compileElement(myObjectCollection, 20, 1, "item in collection | itemsPerPage: itemsPerPage"); + var paginationLinks = getPageLinksArray(); + + expect(paginationLinks).toEqual(['‹','1', '2', '3', '4', '5', '›']); + }); + }); describe('valid expressions', function() { @@ -543,6 +557,23 @@ describe('dirPagination directive', function() { expect(pageLinks).toEqual(['‹','1', '2', '3', '...', '10', '›']); }); + it('should alter links array when value of max-size changes', function() { + $scope.maxSize = 5; + compileWithAttributes(' max-size="maxSize" '); + + var pageLinks = getPageLinksArray(); + + expect(pageLinks).toEqual(['‹','1', '2', '3', '...', '10', '›']); + + $scope.$apply(function() { + $scope.maxSize = 9; + }); + + pageLinks = getPageLinksArray(); + + expect(pageLinks).toEqual(['‹','1', '2', '3', '4', '5', '6', '7', '...', '10', '›']); + }); + it('should impose a minimum max-size of 5', function() { compileWithAttributes(' max-size="2" '); @@ -637,6 +668,15 @@ describe('dirPagination directive', function() { $scope.$apply(); expect($scope.myCallback).toHaveBeenCalledWith(2); }); + + it('should pass the previous page number to the callback', function() { + compileWithAttributes(' on-page-change="myCallback(oldPageNumber)" '); + var pagination = containingElement.find('ul.pagination'); + + pagination.children().eq(3).find('a').triggerHandler('click'); + $scope.$apply(); + expect($scope.myCallback).toHaveBeenCalledWith(1); + }); }); describe('total-items attribute', function() { @@ -1093,7 +1133,7 @@ describe('dirPagination directive', function() { compile(); var $list1 = containingElement.find('ul.list').eq(0); - var $list2 = containingElement.find('ul.list').eq(1); + var $list2 = containingElement.find('ul.list').eq(1); expect(getListItems($list1)).toEqual([ '1', '2', '3' ]); expect(getListItems($list2)).toEqual([ 'a', 'b', 'c' ]); @@ -1108,6 +1148,52 @@ describe('dirPagination directive', function() { expect(getListItems($list2)).toEqual([ 'a', 'b', 'c' ]); }); + }); + + describe('paginationTemplateProvider', function() { + + beforeEach(inject(function($templateCache) { + $templateCache.put('setPath_template', '
Test Template{{ pages.length }}
'); + $templateCache.put('templateUrl_template', '
Test TemplateUrl Template{{ pages.length }}
'); + })); + + it('should use the custom template specified by setPath()', function() { + templateProvider.setPath('setPath_template'); + compileElement(myCollection, 10); + + expect(containingElement.find('.set-path-template').html()).toContain('Test Template'); + }); + + it('should use the custom template specified by setString()', function() { + templateProvider.setString('
Test Template String{{ pages.length }}
'); + compileElement(myCollection, 10); + + expect(containingElement.find('.set-string-template').html()).toContain('Test Template String'); + }); + + it('should prioritize setString() if both path and string have been set', function() { + templateProvider.setString('
Test Template String{{ pages.length }}
'); + templateProvider.setPath('setPath_template'); + compileElement(myCollection, 10); + + expect(containingElement.find('.set-path-template').html()).toBeUndefined(); + expect(containingElement.find('.set-string-template').html()).toContain('Test Template String'); + }); + + it('should prioritize setString() over path and template-url attribute.', function() { + templateProvider.setString('
Test Template String{{ pages.length }}
'); + templateProvider.setPath('setPath_template'); + + var html = '
  • {{ item }}
' + + ''; + containingElement.append($compile(html)($scope)); + $scope.$apply(); + + expect(containingElement.find('.set-path-template').html()).toBeUndefined(); + expect(containingElement.find('.template-url-template').html()).toBeUndefined(); + expect(containingElement.find('.set-string-template').html()).toContain('Test Template String'); + }); }); + }); diff --git a/src/directives/uiBreadcrumbs/README.md b/src/directives/uiBreadcrumbs/README.md index 67d30d9..f91d7b9 100644 --- a/src/directives/uiBreadcrumbs/README.md +++ b/src/directives/uiBreadcrumbs/README.md @@ -2,6 +2,8 @@ This is a directive that auto-generates breadcrumbs based on angular-ui-router routes. +**No longer maintained** (July 2016) I am no longer using this in any of my projects. As such, I have no time to maintain it. Pull requests for fixes and features are welcome. If someone would like to maintain a fork, let me know and I will link to it here. + ## Demo You can see a working demo demonstrating most of the features here: [http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview](http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview) @@ -18,9 +20,10 @@ nested views, you should use a named view and refer to it when configuring your ## Installation ### 1. Download -You can install with Bower: +You can install with Bower or npm: `bower install angular-utils-ui-breadcrumbs` +`npm install angular-utils-ui-breadcrumbs` Alternatively just download the files `uiBreadcrumbs.js` and `uiBreadcrumbs.tpl.html`. Using bower has the advantage of making version management easier. diff --git a/src/filters/ordinalDate/ordinalDate.js b/src/filters/ordinalDate/ordinalDate.js index 553edae..6afa684 100644 --- a/src/filters/ordinalDate/ordinalDate.js +++ b/src/filters/ordinalDate/ordinalDate.js @@ -5,59 +5,18 @@ angular.module( 'angularUtils.filters.ordinalDate', [] ) var getOrdinalSuffix = function(number) { var suffixes = ["'th'", "'st'", "'nd'", "'rd'"]; var relevantDigits = (number < 30) ? number % 20 : number % 30; - return (relevantDigits <= 3) ? suffixes[relevantDigits] : suffixes[0]; - }; - - /** - * Look through the format string for any possible match for 'd'. - * It needs to ignore 'dd' and also occurrences of the letter d inside - * string such as "d 'day of' MM'. - * @param format - */ - var getIndecesOfDayCharacter = function(format) { - var dayRegex = /(?:'(?:[^']|'')*')|(?:d+)/g; - var matchingIndices = []; - var finishedLooking = false; - - while(!finishedLooking) { - var matches = dayRegex.exec(format); - if (matches) { - dayRegex.lastIndex = matches.index + matches[0].length; - if (matches[0] === 'd') { - matchingIndices.push(matches.index + 1); - } - } else { - finishedLooking = true; - } - } - - return matchingIndices; - }; - - /** - * Insert a string at a given index of another string - * @param inputString - * @param index - * @param stringToInsert - * @returns {string} - */ - var insertAtIndex = function(inputString, index, stringToInsert) { - var partBeforeIndex = inputString.substring(0, index); - var partAfterIndex = inputString.substring(index, inputString.length); - return partBeforeIndex + stringToInsert + partAfterIndex; + return "d" + ((relevantDigits <= 3) ? suffixes[relevantDigits] : suffixes[0]); }; return function(timestamp, format) { + var regex = /d+((?!\w*(?=')))|d$/g; var date = new Date(timestamp); var dayOfMonth = date.getDate(); var suffix = getOrdinalSuffix(dayOfMonth); - var matchingIndices = getIndecesOfDayCharacter(format); - - // now we to insert the suffix at the index(-ces) that we found - for (var i = matchingIndices.length; i > 0; i --) { - format = insertAtIndex(format, matchingIndices[i-1], suffix); - } + format = format.replace(regex, function (match) { + return match === "d" ? suffix : match; + }); return $filter('date')(date, format); }; }]); \ No newline at end of file