Initial commity

This commit is contained in:
Martin Donnelly 2017-06-09 09:09:06 +01:00
commit 854d0e2a4c
1209 changed files with 1544019 additions and 0 deletions

13
.babelrc Normal file
View File

@ -0,0 +1,13 @@
{
"sourceMap": true,
"moduleIds": false,
"comments": false,
"compact": false,
"code": true,
"presets": [ ["es2015", {"loose": true}], "stage-1"],
"plugins": [
"syntax-flow",
"transform-decorators-legacy",
"transform-flow-strip-types"
]
}

20
.editorconfig Normal file
View File

@ -0,0 +1,20 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# 2 space indentation
[**.js]
indent_style = space
indent_size = 2
# 4 space indentation
[**.html]
indent_style = space
indent_size = 4

9
.eslintrc.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/aurelia-tools/.eslintrc.json",
"rules": {
"dot-notation": ["warn"],
"no-unused-vars": ["error"],
"no-console": ["error"],
"no-debugger": ["error"]
}
}

180
.gitignore vendored Normal file
View File

@ -0,0 +1,180 @@
# Created by .ignore support plugin (hsz.mobi)
### Archives template
# It's better to unpack these files and commit the raw source because
# git has its own built in compression methods.
*.7z
*.jar
*.rar
*.zip
*.gz
*.bzip
*.bz2
*.xz
*.lzma
*.cab
#packing-only formats
*.iso
*.tar
#package management formats
*.dmg
*.xpi
*.gem
*.egg
*.deb
*.rpm
*.msi
*.msm
*.msp
### Windows template
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### OSX template
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
bower_components
### VisualStudioCode template
.settings
### Xcode template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
dist

27
.jsbeautifyrc Normal file
View File

@ -0,0 +1,27 @@
{
"indent_size": 4,
"html": {
"end_with_newline": true,
"js": {
"indent_size": 2
},
"css": {
"indent_size": 4
}
},
"css": {
"preserve_newlines": true,
"end_with_newline": true,
"space_in_paren": true,
"newline_between_rules": true,
"space_around_selector_separator": true,
"space_around_combinator": true
},
"js": {
"indent_size": 2,
"end_with_newline": true,
"max_preserve_newlines": 2,
"keep_array_indentation": true,
"space_in_paren": true
}
}

10
.jsinspectrc Normal file
View File

@ -0,0 +1,10 @@
{
"threshold": 30,
"identifiers": true,
"literals": true,
"color": true,
"minInstances": 2,
"ignore": "test|spec.js|mock|.json",
"reporter": "default",
"truncate": 100
}

11
.project Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>web-platform</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

13
.stylelintrc Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4,
"block-no-empty": null,
"at-rule-no-vendor-prefix": true,
"media-feature-name-no-vendor-prefix": true,
"property-no-vendor-prefix": true,
"selector-no-vendor-prefix": true,
"value-no-vendor-prefix": true,
"no-duplicate-selectors": true
}
}

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:9000",
"webRoot": "${workspaceRoot}"
}
]
}

43
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,43 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"command": "npm",
"isShellCommand": true,
"showOutput": "always",
"suppressTaskName": true,
"tasks": [
{
"taskName": "install",
"args": ["install"]
},
{
"taskName": "update",
"args": ["update"]
},
{
"taskName": "au-build",
"args": ["run", "au-build"]
},
{
"taskName": "au-build-dev",
"args": ["run", "au-build-dev"]
},
{
"taskName": "au-test",
"args": ["run", "au-test"]
},
{
"taskName": "au-run",
"args": ["run", "au-run"]
},
{
"taskName": "au-run-dev",
"args": ["run","au-run-dev"]
},
{
"taskName": "au-lint-js",
"args": ["run", "au-lint-js"]
}
]
}

BIN
Dungeon8807construction.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

98
README.md Normal file
View File

@ -0,0 +1,98 @@
# Installation and set up
## prerequisites
1. Nodejs > 6.5
2. Git
## steps
1. npm install aurelia-cli -g
2. npm install
3. ~~copy built assets folder from responsive-web to assets~~
4. au run --watch
5. au test
## build details
The build creates web-platform.js in the assets/js folder
## commands
### `au run`
Runs the build process, then starts a local dev server on port 9000 i.e. http://localhost:9000/
* Add flag `--js` to only rebuild javascript and markup when developing. This speeds up the rebuild.
* ~~Add flag `--alan` to only rebuild css, assets and markup when developing. This speeds up the rebuild.~~
* Add flag `--dev` to only transpile js and process markup without linting when developing. This speeds up the rebuild. Run a full build before committing.
* Add flag `--docs` to only build the markdown files. This speeds up the documentation.
Run a full build before committing.
### `au run --watch`
Same as `au run`, but will rebuild when code changes
See `au run` task for `--js` and `--docs` and `--dev` flags
### `au run --watch --use-test-server`
Same as `au run --watch --use-test-server`, but will use test server data
Type `au help run` for more details
### `au build`
Runs the build process. This includes linting, processing, transpiling and bundling, but not tests
See `au run` task for `--js` and `--docs` and `--dev` flags
### `au test`
Runs ALL test suites (*.spec.js files).
~~Append a spec filename to this command to run that suite only e.g. `au test src\components\products\common\elements\ft-footnote.spec.js`~~
### `au test --ui`
Runs test suites for UI components using Jasmine and Karma. This will open and close a browser window while running tests.
~~Append a spec filename to this command to run that suite only e.g. `au test src\components\products\common\elements\ft-footnote.spec.js --ui`~~
NB: Doesn't run the Business Logic (reducer) test suites.
### `au test --bl`
Runs test suites for Business Logic JS files (reducers, value-converters etc) using Jest.
~~Append a spec filename to this command to run that suite only e.g. `au test --bl src\components\products\distributions\ft-distributions\lib\en-us-retail\distribution-rate.spec.js`~~
NB: These tests run very fast, as they only use NodeJS.
### `au lint-css`
Lints all `scss` files within `src` folder using http://stylelint.io/
Configuration is based on [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard) with custom configuration in `.stylelintrc` file
### `au lint-js`
Uses [ESLint](http://eslint.org/) to lint all `.js` files within `src` and `test` folders.
Configuration is on '.eslintrc' and based on the Aurelia eslint configuration.
### `au lint-markup`
Lints html files using [gulp-lintspaces](https://www.npmjs.com/package/gulp-lintspaces)
All this really lints is whitespace and indentation, but at the moment there aren't any suitable html snippet linters.
### `au process-assets`
At present, this just copies the bootstrap fonts from the `node_modules` folder to `assets`.
In future it will likely copy more font and image files as part of the build.
### `au process-css`
This applies the following to the scss files in the `src` folder:
* runs the `lint-css` task
* compiles the sass into css
* applies [autoprefixer](https://github.com/postcss/autoprefixer) via [postcss](https://github.com/postcss/postcss)
* minifies the css via [cssnano](http://cssnano.co/)
* generates css sourcemaps
* saves the css files and sourcemaps to the `assets` folder
### `au process-markup`
This just applies the `lint-markup` task, before passing the markup off to the bundler
### `au generate redux-component`
This is used to generate a redux component or sub-component. Tests, documentation, sass and (optionally) reducer files are also generated. See `lib/redux-component-base.md` for more details.
### `au proxy-server`
This is used to test the dev assets against the live or dev site.
Example proxying live site with dev css assets
au proxy-server --proxy-target http://rcovlnx0188:8202 --dev-css-assets-server http://pattern-library.corp.frk.com/
Example proxying dev site with dev css assets
au proxy-server --dev-css-assets-server http://pattern-library.corp.frk.com/
Example proxying live site with dev js assets, and aurelia etf config
au proxy-server --override-aurelia-config configuration/etf/main --dev-js-assets-server http://localhost:9000/

319
assets/docs.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,59 @@
function downloadPdfApplication(data , id, option, site) {
var j ;
var i ;
var val;
var fid;
var cid;
var ls;
var fs;
var fill;
var d;
var path;
var file;
var pdfNumtool;
for(j=0; j<=data.length; j++){
if(data[j].checked == true){
if(site == "OF"){
val = data[j].id;
i = j+1;
fid = document.getElementById('fid'+id+i).value;
cid = document.getElementById('cid'+id+i).value;
fs = document.getElementById('fs'+id+i).value;
ls = document.getElementById('ls'+id+i).value;
fill = document.getElementById('fill'+id+i).value;
d = document.getElementById('d'+id).value;
path = document.getElementById('path'+id+i).value;
} else{
val = data[j].id;
fid = document.getElementById('fid'+id).value;
cid = document.getElementById('cid'+id).value;
fs = document.getElementById('fs'+id).value;
ls = document.getElementById('ls'+id).value;
fill = document.getElementById('fill'+id).value;
d = document.getElementById('d'+id).value;
path = document.getElementById('path'+id).value;
}
file = d +".pdf";
//alert(val+fid+cid+fs+ls+fill+d+path);
pdfNumtool=document.getElementById('pdfNumtool').value;
if(option=="true"){
file = "1.pdf";
document.downloadAccApp.action = pdfNumtool+"?fid=" + fid + "&cid=" + cid + "&fs=" + fs + "&ls=" + ls + "&fill=1&d=1"+"&path="+path+file;
}else{
document.downloadAccApp.action = pdfNumtool+"?fid=" + fid + "&cid=" + cid + "&fs=" + fs + "&ls=" + ls + "&fill=" + fill + "&d="+d+"&path="+path+file;
}
document.downloadAccApp.submit();
break;
}
}
}
//fixes chrome browser issue with checked input values
$(document).ready(function () {
$('form input[type="radio"]').each(function () {
if ($(this).attr("data-hack") == 'checked') {
$(this).attr('checked', true);
}
});
});

2166
assets/js/jquery-ui.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,636 @@
'use strict';
function downloadPdfApplication(data, id, option, site) {
var j = void 0;
var i = void 0;
var fid = void 0;
var cid = void 0;
var ls = void 0;
var fs = void 0;
var fill = void 0;
var d = void 0;
var path = void 0;
var file = void 0;
var pdfNumtool = void 0;
for (j = 0; j <= data.length; j++) {
if (data[j].checked === true) {
if (site === 'OF') {
i = j + 1;
fid = document.getElementById('fid' + id + i).value;
cid = document.getElementById('cid' + id + i).value;
fs = document.getElementById('fs' + id + i).value;
ls = document.getElementById('ls' + id + i).value;
fill = document.getElementById('fill' + id + i).value;
d = document.getElementById('d' + id).value;
path = document.getElementById('path' + id + i).value;
} else {
fid = document.getElementById('fid' + id).value;
cid = document.getElementById('cid' + id).value;
fs = document.getElementById('fs' + id).value;
ls = document.getElementById('ls' + id).value;
fill = document.getElementById('fill' + id).value;
d = document.getElementById('d' + id).value;
path = document.getElementById('path' + id).value;
}
file = d + '.pdf';
pdfNumtool = document.getElementById('pdfNumtool').value;
if (option === 'true') {
file = '1.pdf';
document.downloadAccApp.action = pdfNumtool + '?fid=' + fid + '&cid=' + cid + '&fs=' + fs + '&ls=' + ls + '&fill=1&d=1' + '&path=' + path + file;
} else {
document.downloadAccApp.action = pdfNumtool + '?fid=' + fid + '&cid=' + cid + '&fs=' + fs + '&ls=' + ls + '&fill=' + fill + '&d=' + d + '&path=' + path + file;
}
document.downloadAccApp.submit();
break;
}
}
}
$(document).ready(function () {
$('form input[type="radio"]').each(function () {
if ($(this).attr('data-hack') === 'checked') {
$(this).attr('checked', true);
}
});
});
'use strict';
(function ($, document, window) {
var componentName = 'backToTop';
var defaults = {
offset: '300',
duration: '300' };
function Component(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = componentName;
this.btnTopScroll = $(this.element).find('.back-to-top');
this.init();
}
Component.prototype = {
init: function init() {
var _this = this;
this.btnTopScroll.click(function (event) {
event.preventDefault();
_this.scroll(_this._defaults.duration);
return false;
});
$(window).scroll(function () {
if ($(window).scrollTop() > _this._defaults.offset) {
$('.back-to-top').fadeIn(_this._defaults.duration);
} else {
$('.back-to-top').fadeOut(_this._defaults.duration);
}
});
},
scroll: function scroll(duration) {
$('html, body').animate({ scrollTop: 0 }, duration);
}
};
$.fn[componentName] = function (options) {
return this.each(function () {
if (!$.data(this, 'component_' + componentName)) {
$.data(this, 'component_' + componentName, new Component(this, options));
} else {
$.data(this, 'component_' + componentName).scroll();
}
});
};
})(jQuery, document, window);
'use strict';
(function ($) {
function _getActiveSlideTilte() {
var slideTitle = $('[data-fti-component="carousel"]').find($('#ft-carousel')).find($('.item.active'))[0].attributes['slide-title'].value;
return slideTitle;
}
function _getSlidesNames() {
var slides = $('[data-fti-component="carousel"]').find($('#ft-carousel')).find($('.item'));
var slideNames = [];
for (var i = 0; i < slides.length; i++) {
var slide = slides[i];
slideNames.push(slide.attributes['slide-title'].value);
}
return slideNames;
}
function _getActiveSlideNR(activeSlide, slidesNames) {
if (!activeSlide || !slidesNames) return;
var counter = 0;
var activeNR = 0;
for (var i = 0; i < slidesNames.length; i++) {
var slide = slidesNames[i];
if (slide === activeSlide) {
activeNR = counter;
}
counter++;
}
return activeNR;
}
$.fn.carouselNav = function (direction) {
if (!direction) return;
var activeSlide = _getActiveSlideTilte();
var slideNames = _getSlidesNames();
var activeSlideNR = _getActiveSlideNR(activeSlide, slideNames);
var newSlideNR = void 0;
if (direction === 'left') newSlideNR = activeSlideNR - 1;
if (direction === 'right') newSlideNR = activeSlideNR + 1;
$(this).setNewTitleAndNav(newSlideNR, slideNames);
};
$.fn.setNewTitleAndNav = function (slideNR, slideNames) {
if (!slideNames) slideNames = _getSlidesNames();
var newSlideNR = parseInt(slideNR, 10);
var nrOfSlides = slideNames.length - 1;
var nextSlide = parseInt(slideNR, 10) + 1;
var prevSlide = parseInt(slideNR, 10) - 1;
if (prevSlide < 0) prevSlide = nrOfSlides;
if (newSlideNR < 0) {
newSlideNR = nrOfSlides;
prevSlide--;
}
if (nextSlide > nrOfSlides) nextSlide = 0;
if (newSlideNR > nrOfSlides) {
newSlideNR = 0;
nextSlide++;
}
$('#ft-carousel-header').text(slideNames[newSlideNR]);
$('#carouselLeft').find('span').text(slideNames[prevSlide]);
$('#carouselRight').find('span').text(slideNames[nextSlide]);
};
})(jQuery);
(function ($) {
$.fn.removeActiveFromOWL = function () {
$('li.owl-item.active').removeClass('active');
return;
};
$.fn.removeActiveFromRole = function () {
var presentationTab = $('li.owl-item').find($('[role="presentation"]'));
presentationTab.removeClass('active');
return;
};
$.fn.tabDirectionMove = function (owlTabbedOBJ, direction) {
var activeTab = owlTabbedOBJ.find('.owl-tab.active');
var activeTabName = activeTab.find('a')[0].getAttribute('aria-controls');
var activeTabNR = activeTabName.substring(activeTabName.length - 1);
var tabOperator = 1;
if (direction === 'prev') tabOperator = -1;
var directTabName = activeTabName.substring(0, activeTabName.length - 1) + (parseInt(activeTabNR, 10) + parseInt(tabOperator, 10));
var directTab = owlTabbedOBJ.find('[aria-controls="' + directTabName + '"]');
return directTab;
};
})(jQuery);
$('#carouselLeft').click(function () {
$(this).carouselNav('left');
});
$('#carouselRight').click(function () {
$(this).carouselNav('right');
});
$('[data-target="#ft-carousel"]').click(function () {
if (typeof this.attributes['data-slide-to'] !== 'undefined') {
var card = this.attributes['data-slide-to'].value;
$(this).setNewTitleAndNav(card);
}
});
$('#carousel-announcements').owlCarousel({
loop: false,
margin: 0,
responsiveClass: true,
navElement: 'i class="ft-icon ft-icon-right-carrot"',
navText: '',
responsive: {
0: {
items: 1,
dots: true,
nav: false
},
768: {
items: 2,
dots: false,
nav: true
},
992: {
items: 3,
dots: false,
nav: true
},
1200: {
items: 4,
dots: false,
nav: true
}
}
});
$('#carousel-announcements-tabbed').owlCarousel({
loop: false,
margin: 0,
responsiveClass: true,
navElement: 'i class="tabbed ft-icon ft-icon-right-carrot"',
navText: ['', ''],
itemElement: 'li',
touchDrag: false,
mouseDrag: false,
responsive: {
0: {
items: 1,
dots: true,
nav: false
},
768: {
items: 2,
dots: false,
nav: true
},
992: {
items: 4,
dots: false,
nav: true
},
1200: {
items: 4,
dots: false,
nav: true
}
},
onInitialized: rmActive,
onResized: function onResized() {
rmActive;
$('#carousel-announcements-tabbed').trigger('to.owl.carousel', 0);
$('#carousel-announcements-tabbed').find('[aria-controls="tabs-content-1"]').trigger('click');
}
});
function rmActive() {
$('#carousel-announcements-tabbed').removeActiveFromOWL();
}
$('.owl-next').click(function () {
var owlTabbed = $('#carousel-announcements-tabbed');
owlTabbed.removeActiveFromOWL();
var nextTab = owlTabbed.tabDirectionMove(owlTabbed, 'next');
nextTab.trigger('click');
});
$('.owl-prev').click(function () {
var owlTabbed = $('#carousel-announcements-tabbed');
owlTabbed.removeActiveFromOWL();
var prevTab = owlTabbed.tabDirectionMove(owlTabbed, 'prev');
prevTab.trigger('click');
});
$('li.owl-item').find($('[role="presentation"]')).find($('a')).click(function () {
$(this).removeActiveFromRole();
});
$('#carousel-announcements-tabbed').find('.owl-dots').click(function () {
var owlTabbed = $('#carousel-announcements-tabbed');
var activatedTab = owlTabbed.find('.owl-item.active');
var activatedTabLink = activatedTab.find('a');
activatedTabLink.trigger('click');
});
$('[data-fti-component="carousel"]').setNewTitleAndNav('0');
'use strict';
(function ($, document) {
var componentName = 'leavingSite';
var defaults = {
leavingSiteText: 'Clicking OK below will take you to an independent site. Information and services provided on this independent site are not reviewed by, guaranteed by, or endorsed by Franklin Templeton or its affiliates. Please keep in mind that this independent site\'s terms and conditions, privacy and security policies, or other legal information may be different from those of Franklin Templeton\'s site. Franklin Templeton is not liable for any direct or indirect technical or system issues, consequences, or damages arising from your use of this independent website.',
leavingSiteOk: 'OK',
leavingSiteCancel: 'Cancel'
};
function Component(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = componentName;
this.btnaExternalLink = $(this.element);
this.init();
}
Component.prototype = {
init: function init() {
this.btnaExternalLink.click(function (e) {
e.preventDefault();
$('ft-modal')[0].au.controller.viewModel.externalopen(e.target.href);
});
}
};
$.fn[componentName] = function (options) {
return this.each(function () {
if (!$.data(this, 'component_' + componentName)) {
$.data(this, 'component_' + componentName, new Component(this, options));
}
});
};
})(jQuery, document);
'use strict';
(function ($) {
var componentName = 'header';
var defaults = {};
function Component(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = componentName;
this.navTitles = $('#mega').find('.title');
this.init();
}
function isMobile() {
return $('#navbar-hamburger').is(':visible');
}
Component.prototype = {
init: function init() {
var _self = this;
$('ul.nav li.dropdown').on('click touch', function () {
if (!$(this).hasClass('open') && !isMobile()) {
$(this).addClass('currentlyHovering');
if ($('#mega').find('li.open').length === 0) {
$(this).queue(function () {
if ($(this).hasClass('currentlyHovering') && !isMobile()) {
$(this).find('.dropdown-toggle').attr('aria-expanded', 'true');
$(this).addClass('open').dequeue();
if ($(this).hasClass('search')) {
$(this).find('input').focus();
}
} else {
$(this).dequeue();
}
});
} else {
if ($(this).hasClass('currentlyHovering') && !isMobile()) {
$(this).find('.dropdown-toggle').attr('aria-expanded', 'true');
$('#mega').find('li.open').removeClass('open');
$(this).addClass('open').dequeue();
if ($(this).hasClass('search')) {
$(this).find('input').focus();
}
}
}
}
}, function () {
$(this).removeClass('currentlyHovering');
$(this).delay(250).queue(function () {
if (!$(this).is(':hover') && !isMobile()) {
$(this).removeClass('open').dequeue();
$(this).find('.dropdown-toggle').attr('aria-expanded', 'false');
} else {
$(this).dequeue();
}
});
});
$('#slide-nav.navbar').after($('<div id="navbar-height-col"></div>'));
var toggler = '.navbar-toggle';
var pagewrapper = '#page-content';
var navigationwrapper = '.navbar-header';
var slidewidth = '85%';
var menuneg = '-100%';
var slideneg = '-85%';
$('#slide-nav').on('click', toggler, function () {
var selected = $(this).hasClass('offcanvas-active');
$('#navbar-close').toggleClass('hidden');
$('#navbar-hamburger').toggleClass('hidden');
$('#offcanvas-menu').stop().animate({
left: selected ? menuneg : '0px'
});
$('#navbar-height-col').stop().animate({
left: selected ? slideneg : '0px'
});
$(pagewrapper).stop().animate({
left: selected ? '0px' : slidewidth
});
$(navigationwrapper).stop().animate({
left: selected ? '0px' : slidewidth
});
$(this).toggleClass('offcanvas-active', !selected);
$('#offcanvas-menu').toggleClass('offcanvas-active');
$('#page-content, .navbar, body, .navbar-header').toggleClass('offcanvas-active');
});
$('form[data-fti-element=sign-in-form]').on('submit', function (e) {
var _form = this;
e.preventDefault();
var postData = $(_form).serialize();
$.ajax({
type: 'POST',
url: '/api/account/prelogin',
data: postData,
dataType: 'json'
}).done(function (e2, data) {
if (e2.location) {
window.location = e2.location;
} else {
$(_form).attr('action', e2.loginUrl);
$(_form).off('submit');
$(_form).find('input[name="rememberMe"]')[0].value = postData.indexOf('_rememberMe=on') > -1;
$(_form).submit();
}
});
});
var selected = '#slidemenu, #page-content, body, .navbar, .navbar-header';
_self.setToggleCollapse();
_self.resizeHandler(selected);
_self.initTooltip();
},
setToggleCollapse: function setToggleCollapse() {
var _self = this;
if (isMobile()) {
_self.navTitles.data('toggle', 'collapse');
_self.navTitles.on('click', function (e) {
var el = $(e.target);
while (!el.is('a')) {
el = el.parent();
}
el.next('.collapse').collapse('toggle');
el.attr('aria-expanded', function (i, attr) {
return attr === 'true' ? 'false' : 'true';
});
el.parent('.dropdown').toggleClass('open');
e.stopPropagation();
e.preventDefault();
});
} else {
_self.navTitles.data('toggle', 'disabled');
_self.navTitles.off('click');
}
},
resizeHandler: function resizeHandler(selected) {
var _self = this;
$(window).on('resize', function () {
if (!isMobile() && $('.navbar-toggle').is(':hidden')) {
$(selected).removeClass('offcanvas-active');
}
_self.setToggleCollapse();
});
},
initTooltip: function initTooltip() {
$('[data-toggle="tooltip"]').tooltip();
}
};
$.fn[componentName] = function (options) {
return this.each(function () {
if (!$.data(this, 'component_' + componentName)) {
$.data(this, 'component_' + componentName, new Component(this, options));
}
});
};
})(jQuery);
'use strict';
$(function () {
$('*[data-fti-module="header"]').header();
$('*[data-fti-module="back-to-top"]').backToTop();
$('*[data-fti-module="rich_text"]').rich_text();
$('*[data-fti-component="tabs"]').tabs();
$('*[data-fti-component="rich-text"][data-fti-module="tabs"]').tabs();
$('a.external-link').each(function () {
$(this).leavingSite();
});
});
'use strict';
(function ($, window, document) {
var componentName = 'rich_text';
function Component(element, options) {
this.element = element;
this.options = options;
this._name = componentName;
this.init();
}
Component.prototype = {
init: function init() {
var _this = this;
$('.collapseAll', this.element).on('click', function () {
$('.panel-collapse.in', _this.element).collapse('hide');
});
$('.expandAll', this.element).on('click', function () {
$('.panel-collapse:not(".in")', _this.element).collapse('show');
});
}
};
$.fn[componentName] = function (options) {
return this.each(function () {
if (!$.data(this, 'component_' + componentName)) {
$.data(this, 'component_' + componentName, new Component(this, options));
} else {
$.data(this, 'component_' + componentName).init();
}
});
};
})(jQuery, window, document);
'use strict';
(function ($) {
var componentName = 'tabs';
var defaults = {};
function Component(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = componentName;
this.navCollapse = $(this.element).find('.navbar-collapse');
this.navTabLinks = $(this.element).find('.nav-tabs-top-level, .nav.nav-pills.nav-stacked').find('a');
this.tabContent = $(this.element).find('.tab-content');
this.headerTextContainer = $(this.element).find('.mobile-menu + span');
this.init();
}
Component.prototype = {
init: function init() {
var _this = this;
this.setHeaderText();
if (this.navCollapse.find('ul').hasClass('nav-stacked--no-panel')) {
this.adjustContentHeight();
$(window).on('resize', function () {
_this.adjustContentHeight();
});
}
},
setHeaderText: function setHeaderText() {
var _self = this;
this.navTabLinks.on('click', function (e) {
var elParent = $(this).parent('li');
var headerText = $(this).text();
_self.headerTextContainer.text(headerText);
elParent.siblings().removeClass('hidden-xs');
elParent.addClass('hidden-xs');
_self.navCollapse.collapse('hide');
});
},
adjustContentHeight: function adjustContentHeight() {
if (this.headerTextContainer.is(':visible')) {
$(this.tabContent).css('min-height', 'auto');
} else {
var navTabsHeight = this.navCollapse.height();
var bottomMargin = 5;
$(this.tabContent).css('min-height', navTabsHeight - bottomMargin);
}
}
};
$.fn[componentName] = function (options) {
return this.each(function () {
if (!$.data(this, 'component_' + componentName)) {
$.data(this, 'component_' + componentName, new Component(this, options));
}
});
};
})(jQuery);
"use strict";
//# sourceMappingURL=livesite-modules.js.map

File diff suppressed because one or more lines are too long

189
assets/js/local-test.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

34806
assets/js/web-platform-app.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

56562
assets/js/web-platform.js Normal file

File diff suppressed because one or more lines are too long

9
assetsSymLink.bat Normal file
View File

@ -0,0 +1,9 @@
REM Symbolic links solution for web-platform/pattern-lab integration in the same branch
REM To create symbolic links as non admin:
REM 1. Bring up your run box, type “secpol.msc” and click OK.
REM 2. Navigate under the Security Settings \ Local Policies \ User Rights Assignment folder.
REM 3. Find “Create symbolic links” and add the Users group to it.
REM 4. Restart system.
mklink /D assets ..\pattern-library\public\assets
mklink /D ..\pattern-library\public\data ..\..\web-platform\test\data

View File

@ -0,0 +1,348 @@
{
"name": "web-platform",
"source": "cli",
"path": "C:\\Users\\bstjohn\\Documents\\eclipse\\GW",
"folder": "C:\\Users\\bstjohn\\Documents\\eclipse\\GW",
"type": "project:application",
"transpiler": {
"id": "babel",
"displayName": "Babel",
"fileExtension": ".js",
"options": {
"plugins": [
"transform-es2015-modules-amd"
]
},
"source": "src/**/*.js"
},
"livesiteJS": {
"id": "babel",
"displayName": "Babel",
"fileExtension": ".js",
"options": {
"presets": [
"es2015"
]
},
"source": [
"src/livesite/**/*.js"
],
"output": "assets/js"
},
"jsLinter": {
"id": "eslint",
"displayName": "ESLint",
"fileExtension": ".js",
"source": [
"src/**/*.js",
"test/**/*.js",
"!src/**/*.spec.js",
"!test/aurelia-karma.js",
"!test/**/*.spec.js",
"!node_modules/**",
"!src/livesite/application_form_download.js"
],
"testSource": [
"aurelia_project/**/*.js",
"src/**/*.spec.js",
"test/**/*.spec.js",
"!node_modules/**"
]
},
"markupProcessor": {
"id": "none",
"displayName": "None",
"fileExtension": ".html",
"source": "src/**/*.html"
},
"cssProcessor": {
"id": "sass",
"displayName": "SASS",
"fileExtension": ".scss",
"source": [
"src/**/*.scss",
"!src/**/lib/*"
],
"output": "assets/css",
"browsers": {
"browsers": [
"last 1 version"
]
},
"sassIncludePaths": [
"./node_modules/bootstrap-sass/assets/stylesheets/"
]
},
"docsProcessor": {
"source": [
"README.md",
"src/**/*.md"
],
"outputDir": "assets/",
"jsonFile": "docs.json",
"index": {
"index": [
{
"section": "General",
"regex": "^README.md|developer-notes/general|lib"
},
{
"section": "Common Components",
"regex": "^components/common"
},
{
"section": "Marketing",
"regex": "^components/marketing"
},
{
"section": "Products",
"subs": [
{
"section": "General",
"regex": "^developer-notes/products"
},
{
"section": "Common components",
"regex": "^components/products/common/elements"
},
{
"section": "Value converters",
"regex": "^components/products/common/value-converters"
},
{
"section": "PPSS",
"regex": "^components/products/ppss"
},
{
"section": "Overview",
"regex": "^components/products/overview"
},
{
"section": "Portfolio",
"regex": "^components/products/portfolio"
},
{
"section": "Performance",
"regex": "^components/products/performance|components/products/historical"
},
{
"section": "Pricing & Distribution",
"regex": "^components/products/pricing-distribution"
},
{
"section": "Price",
"regex": "^components/products/pricing"
},
{
"section": "Distributions",
"regex": "^components/products/distributions"
},
{
"section": "Documents",
"regex": "^components/products/documents"
}
]
},
{
"section": "Uncategorized"
}
]
}
},
"assetsProcessor": {
"id": "assets",
"displayName": "Assets",
"clean": "assets",
"assets": [
{
"src": [
"./static-assets/js/**"
],
"dest": "assets/js"
}
]
},
"packager": {
"src": [
"assets/**/*",
"coverage/**/*",
"test/**/*",
"index.html",
"docs.html"
],
"zipfile": "web-platform.zip",
"outputDir": "."
},
"unitTestRunner": {
"id": "karma",
"displayName": "Karma",
"source": "src/**/*.js"
},
"testFramework": {
"id": "jasmine",
"displayName": "Jasmine"
},
"editor": {
"id": "atom",
"displayName": "Atom"
},
"platform": {
"id": "default",
"displayName": "Default",
"output": "assets/js"
},
"workflow": null,
"defaultOrCustom": "custom",
"paths": {
"root": "src"
},
"build": {
"targets": [
{
"id": "default",
"displayName": "Default",
"output": "assets/js",
"useAbsolutePath": true
}
],
"loader": {
"type": "require",
"configTarget": "web-platform.js",
"config": {
"waitSeconds": 60
},
"includeBundleMetadataInConfig": "auto",
"plugins": [
{
"name": "text",
"extensions": [
".html",
".css"
],
"stub": true
}
]
},
"options": {
"minify": "stage & prod",
"sourcemaps": "dev & stage",
"bundleReport": "dev & stage & prod"
},
"bundles": [
{
"name": "local-test.js",
"source": {
"include": [
"[**/test/**/*.js]",
"**/test/**/*.{css,html}"
]
},
"dependencies": [
],
"options": {
"inject": true
}
},
{
"name": "web-platform-app.js",
"source": {
"include": [
"[**/*.js]",
"**/*.{css,html}"
],
"exclude": [
"**/*.spec.js",
"**/test/**/*.*",
"**/livesite/**/*.*"
]
},
"options": {
"inject": true
}
},
{
"name": "web-platform.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"node_modules/whatwg-fetch/fetch.js",
"node_modules/requirejs/require.js"
],
"dependencies": [
{
"name": "text",
"path": "../node_modules/requirejs-text/text",
"packageRoot": "../"
},
"aurelia-binding",
"aurelia-bootstrapper",
"aurelia-dependency-injection",
"aurelia-event-aggregator",
"aurelia-fetch-client",
"aurelia-framework",
"aurelia-loader",
"aurelia-loader-default",
"aurelia-logging",
"aurelia-logging-console",
"aurelia-metadata",
"aurelia-pal",
"aurelia-pal-browser",
"aurelia-path",
"aurelia-polyfills",
{
"name": "redux",
"path": "../node_modules/redux/dist/redux.min"
},
"aurelia-task-queue",
"aurelia-templating",
"aurelia-templating-binding",
{
"name": "aurelia-testing",
"path": "../node_modules/aurelia-testing/dist/amd",
"main": "aurelia-testing"
},
{
"name": "aurelia-templating-resources",
"path": "../node_modules/aurelia-templating-resources/dist/amd",
"main": "aurelia-templating-resources"
},
{
"name": "lodash",
"path": "../node_modules/lodash",
"main": "noop"
},
"jquery",
{
"name": "jquery-ui",
"path": "../static-assets/js/",
"main": "jquery-ui",
"deps": ["jquery"],
"packageRoot": "../static-assets"
},
"attrchange",
"highcharts",
"moment",
{
"name": "moment-parseformat",
"path": "../node_modules/moment-parseformat/dist/moment-parseformat"
},
"bootstrap-sass",
"owl.carousel",
"responsive-toolkit"
],
"options": {
"inject": true
}
}
]
},
"sonar": {
"id": "sonar-runner",
"sonarURL": "http://rcolnx89006:9005",
"sonarDB": "jdbc:mysql://rcolnx89006:3306/sonar?useUnicode=true&amp;characterEncoding=utf8",
"sonarUser": "sonar",
"sonarPasswd": "sonar",
"sources": "src/components,src/lib,src/livesite",
"exclusions": "**/*.md,**/*.spec.js,**/*.html,**/*.json",
"reportPath": "coverage/sonar_report/lcov.info"
}
}

View File

@ -0,0 +1,6 @@
export default {
debug: true,
testing: true,
cssRoot: 'http://pattern-library.corp.frk.com'
// cssRoot: 'http://localhost:3000'
};

View File

@ -0,0 +1,5 @@
export default {
debug: false,
testing: false,
cssRoot: ''
};

View File

@ -0,0 +1,5 @@
export default {
debug: true,
testing: false,
cssRoot: ''
};

View File

@ -0,0 +1,44 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
@inject(Project, CLIOptions, UI)
export default class AttributeGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What would you like to call the custom attribute?')
.then(name => {
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
this.project.attributes.add(
ProjectItem.text(`${fileName}.js`, this.generateSource(className))
);
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
}
generateSource(className) {
return `import {inject} from 'aurelia-framework';
@inject(Element)
export class ${className}CustomAttribute {
constructor(element) {
this.element = element;
}
valueChanged(newValue, oldValue) {
}
}
`;
}
}

View File

@ -0,0 +1,4 @@
{
"name": "attribute",
"description": "Creates a custom attribute class and places it in the project resources."
}

View File

@ -0,0 +1,41 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
@inject(Project, CLIOptions, UI)
export default class BindingBehaviorGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What would you like to call the binding behavior?')
.then(name => {
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
this.project.bindingBehaviors.add(
ProjectItem.text(`${fileName}.js`, this.generateSource(className))
);
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
}
generateSource(className) {
return `export class ${className}BindingBehavior {
bind(binding, source) {
}
unbind(binding, source) {
}
}
`;
}
}

View File

@ -0,0 +1,4 @@
{
"name": "binding-behavior",
"description": "Creates a binding behavior class and places it in the project resources."
}

View File

@ -0,0 +1,288 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
import mkdirp from 'mkdirp';
@inject(Project, CLIOptions, UI)
export default class ElementGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What is the name of the element? (in hyphen format. ft- will be prefixed automatically)')
.then(name => {
let area = 'common/';
let subarea = 'elements/';
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
// let funcName = this.project.makeFunctionName(name);
let subDir = (subarea) ? area + '/' + subarea : area;
let relDir = 'components/' + subDir + '/ft-' + fileName;
let dir = 'src/' + relDir;
mkdirp.sync(dir);// quick fix incase parent folder doesn't exist.
this.project.locations.push(this.project[dir] = ProjectItem.directory(dir));
this.project[dir].add(
ProjectItem.text(`ft-${fileName}.js`, this.generateComponentJsSource(className)),
ProjectItem.text(`ft-${fileName}.spec.js`, this.generateComponentTestJsSource(className, fileName, relDir)),
ProjectItem.text(`ft-${fileName}.html`, this.generateComponentHtmlSource(fileName)),
ProjectItem.text(`ft-${fileName}.md`, this.generateComponentMdSource('ft-' + fileName)),
);
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
}
/**
* source for top level component js
* @param className
* @returns {string}
*/
generateComponentJsSource(className) {
return `import {bindable, inject} from 'aurelia-framework';
@inject(Element)
export class Ft${className} {
@bindable nameThisBindable;
constructor(element) {
this.element = element;
}
callBackFunction() {
this.element.innerHTML = this.nameThisBindable;
}
}`;
}
/**
* source for top level component js unit test
* @param className
* @param fileName
* @returns {string}
*/
generateComponentTestJsSource(className, fileName, relDir) {
return `import {StageComponent} from 'aurelia-testing';
import {LogManager} from 'aurelia-framework';
import {bootstrap} from 'aurelia-bootstrapper';
const logger = LogManager.getLogger('${fileName}');
describe('${className}', () => {
let component;
let mockStore = {
dispatch: function(action) {
expect(action.type).toBeDefined();
logger.info('actions should have a type: ' + action.type);
},
subscribe: function(callback) {
logger.info('subscription request to the store');
}
};
let beans = { // see configuration/en-us-retail/beans.js for example
component: {
${className}: []
},
bean: {
}
};
beforeEach(() => {
component = StageComponent
.withResources('${relDir}/ft-${fileName}')
.inView('<ft-${fileName} id="test1" fund-id="817"></ft-${fileName}>');
component.configure = (aurelia) => {
aurelia.container.registerInstance('Store', mockStore);
aurelia.container.registerInstance('Beans', beans);
aurelia.use.standardConfiguration()
.feature('components/common')
.feature('components/products/common');
};
});
it('should render something', done => {
component.create(bootstrap).then(() => {
const testElement = document.getElementById('test1');
expect(testElement.innerText).toMatch(/replace me/);
done();
});
});
afterEach(() => {
component.dispose();
});
});
`;
}
/**
* source for top level component html
* @returns {string}
*/
generateComponentHtmlSource(fileName) {
return `<template>
<div>ft-${fileName}: replace me</div>
</template>
`;
}
/**
* source for reducer
* @param funcName, fileName
*/
generateReducerJsSource(funcName, fileName) {
return `/**
* Data Reducer for ${funcName}
* Takes site specific json data and creates model for components
* after applying business and presentation logic and data mapping
*/
import {LogManager} from 'aurelia-framework';
const logger = LogManager.getLogger('${fileName}');
export function ${funcName}(state, action) {
switch (action.type) {
case 'CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS':
logger.debug('Reducing: CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS');
return Object.assign({}, state, {
${funcName}: action.data
});
default:
return state;
}
}
`;
}
/**
* source for reducer unit test
* @param funcName
*/
generateReducerTestJsSource(funcName, fileName) {
return `import {${funcName}} from './${fileName}.reducer';
describe('${funcName}', () => {
it('should return unchanged state if action does not apply', done => {
let action = {
type: 'ANOTHER_ACTION'
};
let oldState = {};
let newState = ${funcName}(oldState, action, {});
expect(newState).toBe(oldState);
done();
});
it('should return some stuff', done => {
let action = {
type: 'CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS',
data: {
stuff: 'some stuff'
}
};
let newState = ${funcName}({}, action, {});
expect(newState.${funcName}.stuff).toBe('some stuff');
done();
});
});
`;
}
/**
* source for reducer
* @param funcName, fileName
*/
generateAppStateReducerJsSource(funcName, fileName) {
return `/**
* App State Reducer for ${funcName}
* Takes application state data and creates model for components
*/
import {LogManager} from 'aurelia-framework';
const logger = LogManager.getLogger('${fileName}');
export function ${funcName}(state, action) {
switch (action.type) {
case 'SOME_ACTION':
logger.debug('Reducing: SOME_ACTION');
return Object.assign({}, state, {
${funcName}: action.data
});
default:
return state;
}
}
`;
}
/**
* source for reducer unit test
* @param funcName
*/
generateAppStateReducerTestJsSource(funcName, fileName) {
return `import {${funcName}} from './${fileName}.reducer';
describe('${funcName}', () => {
it('should return unchanged state if action does not apply', done => {
let action = {
type: 'ANOTHER_ACTION'
};
let oldState = {};
let newState = ${funcName}(oldState, action, {});
expect(newState).toBe(oldState);
done();
});
it('should return some stuff', done => {
let action = {
type: 'SOME_ACTION',
data: {
stuff: 'some stuff'
}
};
let newState = ${funcName}({}, action, {});
expect(newState.${funcName}.stuff).toBe('some stuff');
done();
});
});
`;
}
/**
* generate markdown stub
* @param fileName
* @returns {string}
*/
generateComponentMdSource(fileName) {
return `# ${ fileName }
## Usage
${'```'}html
<${ fileName} fund-id="817" cid="uniqueId"></${ fileName}>
${'```'}
*The cid is guaranteed to be unique to this page even if multiple instances of the component are added to the same page.*
## Developer notes
`;
}
/**
* generate sass partial stub
* @param fileName
* @returns {string}
*/
generateComponentSassSource(fileName, name) {
return `
// CSS specific to the ${ fileName } component goes here
[data-fti-component="${ name }"] {
}
`;
}
}

View File

@ -0,0 +1,4 @@
{
"name": "element",
"description": "Creates a custom element class and template, placing them in the project resources."
}

View File

@ -0,0 +1,73 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
@inject(Project, CLIOptions, UI)
export default class GeneratorGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What would you like to call the generator?')
.then(name => {
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
this.project.generators.add(
ProjectItem.text(`${fileName}.js`, this.generateSource(className))
);
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
}
generateSource(className) {
return `import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
@inject(Project, CLIOptions, UI)
export default class ${className}Generator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What would you like to call the new item?')
.then(name => {
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
this.project.elements.add(
ProjectItem.text(\`\${fileName}.js\`, this.generateSource(className))
);
return this.project.commitChanges()
.then(() => this.ui.log(\`Created \${fileName}.\`));
});
}
generateSource(className) {
return \`import {bindable} from 'aurelia-framework';
export class \${className} {
@bindable value;
valueChanged(newValue, oldValue) {
}
}
\`
}
}
`;
}
}

View File

@ -0,0 +1,4 @@
{
"name": "generator",
"description": "Creates a generator class and places it in the project generators folder."
}

View File

@ -0,0 +1,355 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
import mkdirp from 'mkdirp';
@inject(Project, CLIOptions, UI)
export default class ReduxComponentGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What is the name of the redux component? (in hyphen format. ft- will be prefixed automatically)')
.then(name => {
let area = 'products';
this.ui.ensureAnswer(this.options.args[1], 'Which sub area? (blank sub area will add it at the area level)', 'portfolio')
.then(subarea => {
this.ui.ensureAnswer(this.options.args[2], 'Do you want a site specific data reducer? (Y/N)', 'Y')
.then(siteSpecificReducer => {
this.ui.ensureAnswer(this.options.args[3], 'Do you want an app state reducer? (Y/N)', 'N')
.then(appStateReducer => {
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
let funcName = this.project.makeFunctionName(name);
let makeSiteSpecificReducer = (siteSpecificReducer.toLowerCase() === 'y');
let makeAppStateReducer = (appStateReducer.toLowerCase() === 'y');
let subDir = (subarea) ? area + '/' + subarea : area;
let relDir = 'components/' + subDir + '/ft-' + fileName;
let dir = 'src/' + relDir;
mkdirp.sync(dir);// quick fix incase parent folder doesn't exist.
this.project.locations.push(this.project[dir] = ProjectItem.directory(dir));
this.project[dir].add(
ProjectItem.text(`ft-${fileName}.js`, this.generateComponentJsSource(className)),
ProjectItem.text(`ft-${fileName}.spec.js`, this.generateComponentTestJsSource(className, fileName, relDir, funcName)),
ProjectItem.text(`ft-${fileName}.html`, this.generateComponentHtmlSource(fileName)),
ProjectItem.text(`ft-${fileName}.md`, this.generateComponentMdSource('ft-' + fileName))
// ProjectItem.text(`_ft-${fileName}.scss`, this.generateComponentSassSource('ft-' + fileName, name))
);
if (makeSiteSpecificReducer) {
let reducerDir = dir + '/lib/en-us-retail';
mkdirp.sync(reducerDir);// quick fix incase parent folder doesn't exist.
this.project.locations.push(this.project[reducerDir] = ProjectItem.directory(reducerDir));
this.project[reducerDir].add(
ProjectItem.text(`${fileName}.reducer.js`, this.generateReducerJsSource(funcName, fileName)),
ProjectItem.text(`${fileName}.spec.js`, this.generateReducerTestJsSource(funcName, fileName))
);
}
if (makeAppStateReducer) {
let reducerDirAppState = dir + '/lib';
mkdirp.sync(reducerDirAppState);// quick fix incase parent folder doesn't exist.
this.project.locations.push(this.project[reducerDirAppState] = ProjectItem.directory(reducerDirAppState));
this.project[reducerDirAppState].add(
ProjectItem.text(`${fileName}.reducer.js`, this.generateAppStateReducerJsSource(funcName, fileName)),
ProjectItem.text(`${fileName}.spec.js`, this.generateAppStateReducerTestJsSource(funcName, fileName))
);
}
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
});
});
});
}
/**
* source for top level component js
* @param className
* @returns {string}
*/
generateComponentJsSource(className) {
return `import {inject} from 'aurelia-framework';
import {ReduxComponentBase} from '../../../../lib/redux-component-base';
//import {getFundState} from '../../lib/map-state-utils';
@inject('Store', 'Beans', Element)
export class Ft${className} extends ReduxComponentBase {
populated = false;
constructor(store, beans, element) {
// add logging and integration with the data store
super(store);
this.fundId = element.getAttribute('fund-id');
// This is a top level component so we need to initiate the population
this.dispatch({
type: 'POPULATE_CHANGE_ME_STATE',
// beans: beans..., // beans set in configuration for site e.g. configuration/en-us-retail/beans.js
fundId: this.fundId
});
}
/**
* Called when the state changes.
*/
mapState(newState) {
try {
this.logger.debug('mapState()');
//let fundState = getFundState(newState, this.fundId);
// set component properties from state as appropriate
//this.label = newState.products.distributions.label;
//this.proximity = fundState.distributions.caveats.proximity;
// if we get to here without erroring, data has been populated successfully
this.populated = true;
} catch (e) {
// state not populated yet
this.populated = false;
}
}
}
`;
}
/**
* source for top level component js unit test
* @param className
* @param fileName
* @returns {string}
*/
generateComponentTestJsSource(className, fileName, relDir, funcName) {
return `import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
import {wait} from '../../../../lib/test-utils.js';
describe('${className}', () => {
let delay = 10;
let component;
let mockStore = {
dispatch: function(action) {
expect(action.type).toBeDefined();
},
subscribe: function(callback) {
}
};
let mockBeans = { // see configuration/en-us-retail/beans.js for example
component: {
${funcName}: []
},
bean: {
}
};
let mockAttributes;
let mockState;
beforeEach(() => {
spyOn(mockStore, 'dispatch');
mockAttributes = {};
mockState = {};
component = StageComponent
.withResources('${relDir}/ft-${fileName}')
.inView(\`<ft-${fileName} id="test1"></ft-${fileName}>\`);
component.configure = (aurelia) => {
aurelia.container.registerInstance('Store', mockStore);
aurelia.container.registerInstance('Beans', mockBeans);
aurelia.use.standardConfiguration();
};
});
it('should render something', done => {
component.boundTo(mockAttributes);
component.create(bootstrap)
.then(() => {
if (component.viewModel.mapState) {
component.viewModel.mapState(mockState);
}
})
.then(wait(delay))
.then(() => {
const testElement = document.getElementById('test1');
expect(testElement.innerText.trim()).toBe('ft-${fileName}: replace me');
})
.then(done);
});
afterEach(() => {
component.dispose();
});
});
`;
}
/**
* source for top level component html
* @returns {string}
*/
generateComponentHtmlSource(fileName) {
return `<template>
<div>ft-${fileName}: replace me</div>
</template>
`;
}
/**
* source for reducer
* @param funcName, fileName
*/
generateReducerJsSource(funcName, fileName) {
return `/**
* Data Reducer for ${funcName}
* Takes site specific json data and creates model for components
* after applying business and presentation logic and data mapping
*/
import {LogManager} from 'aurelia-framework';
const logger = LogManager.getLogger('${fileName}');
export function ${funcName}(state, action) {
switch (action.type) {
case 'CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS':
logger.debug('Reducing: CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS');
return Object.assign({}, state, {
${funcName}: action.data
});
default:
return state;
}
}
`;
}
/**
* source for reducer unit test
* @param funcName
*/
generateReducerTestJsSource(funcName, fileName) {
return `import {${funcName}} from './${fileName}.reducer';
describe('${funcName}', () => {
it('should return unchanged state if action does not apply', done => {
let action = {
type: 'ANOTHER_ACTION'
};
let oldState = {};
let newState = ${funcName}(oldState, action, {});
expect(newState).toBe(oldState);
done();
});
it('should return some stuff', done => {
let action = {
type: 'CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS',
data: {
stuff: 'some stuff'
}
};
let newState = ${funcName}({}, action, {});
expect(newState.${funcName}.stuff).toBe('some stuff');
done();
});
});
`;
}
/**
* source for reducer
* @param funcName, fileName
*/
generateAppStateReducerJsSource(funcName, fileName) {
return `/**
* App State Reducer for ${funcName}
* Takes application state data and creates model for components
*/
import {LogManager} from 'aurelia-framework';
const logger = LogManager.getLogger('${fileName}');
export function ${funcName}(state, action) {
switch (action.type) {
case 'SOME_ACTION':
logger.debug('Reducing: SOME_ACTION');
return Object.assign({}, state, {
${funcName}: action.data
});
default:
return state;
}
}
`;
}
/**
* source for reducer unit test
* @param funcName
*/
generateAppStateReducerTestJsSource(funcName, fileName) {
return `import {${funcName}} from './${fileName}.reducer';
describe('${funcName}', () => {
it('should return unchanged state if action does not apply', done => {
let action = {
type: 'ANOTHER_ACTION'
};
let oldState = {};
let newState = ${funcName}(oldState, action, {});
expect(newState).toBe(oldState);
done();
});
it('should return some stuff', done => {
let action = {
type: 'SOME_ACTION',
data: {
stuff: 'some stuff'
}
};
let newState = ${funcName}({}, action, {});
expect(newState.${funcName}.stuff).toBe('some stuff');
done();
});
});
`;
}
/**
* generate markdown stub
* @param fileName
* @returns {string}
*/
generateComponentMdSource(fileName) {
return `# ${ fileName }
## Usage
${'```'}html
<${ fileName} fund-id="817" cid="uniqueId"></${ fileName}>
${'```'}
*The cid is guaranteed to be unique to this page even if multiple instances of the component are added to the same page.*
## Developer notes
`;
}
/**
* generate sass partial stub
* @param fileName
* @returns {string}
*/
// generateComponentSassSource(fileName, name) {
// return `
// // CSS specific to the ${ fileName } component goes here
// [data-fti-component="${ name }"] {
// }
// `;
// }
}

View File

@ -0,0 +1,4 @@
{
"name": "redux-component",
"description": "Creates all the source files, and tests for a redux component."
}

View File

@ -0,0 +1,41 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
@inject(Project, CLIOptions, UI)
export default class TaskGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What would you like to call the task?')
.then(name => {
let fileName = this.project.makeFileName(name);
let functionName = this.project.makeFunctionName(name);
this.project.tasks.add(
ProjectItem.text(`${fileName}.js`, this.generateSource(functionName))
);
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
}
generateSource(functionName) {
return `import gulp from 'gulp';
import changed from 'gulp-changed';
import project from '../aurelia.json';
export default function ${functionName}() {
return gulp.src(project.paths.???)
.pipe(changed(project.paths.output, {extension: '.???'}))
.pipe(gulp.dest(project.paths.output));
}
`;
}
}

View File

@ -0,0 +1,4 @@
{
"name": "task",
"description": "Creates a task and places it in the project tasks folder."
}

View File

@ -0,0 +1,41 @@
import {inject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
@inject(Project, CLIOptions, UI)
export default class ValueConverterGenerator {
constructor(project, options, ui) {
this.project = project;
this.options = options;
this.ui = ui;
}
execute() {
return this.ui
.ensureAnswer(this.options.args[0], 'What would you like to call the value converter?')
.then(name => {
let fileName = this.project.makeFileName(name);
let className = this.project.makeClassName(name);
this.project.valueConverters.add(
ProjectItem.text(`${fileName}.js`, this.generateSource(className))
);
return this.project.commitChanges()
.then(() => this.ui.log(`Created ${fileName}.`));
});
}
generateSource(className) {
return `export class ${className}ValueConverter {
toView(value) {
}
fromView(value) {
}
}
`;
}
}

View File

@ -0,0 +1,4 @@
{
"name": "value-converter",
"description": "Creates a value converter class and places it in the project resources."
}

View File

@ -0,0 +1,97 @@
import gulp from 'gulp';
import transpile from './transpile';
import processMarkup from './process-markup';
import processAssets from './process-assets';
import lintJS from './lint-js';
import {build, CLIOptions} from 'aurelia-cli';
import project from '../aurelia.json';
import processDocs from './process-docs';
import livesiteJS from './livesite-js';
import cleanAssets from './clean';
import copyJsFilesToPL from './copy-js';
import copyDataToPL from './copy-data';
export function buildJs(done) {
// TODO : review if --dev still required, since au run --watch now works as expected
if (CLIOptions.hasFlag('dev')) {
return gulp.series(
readProjectConfiguration,
gulp.parallel(
transpile,
livesiteJS,
processMarkup
),
writeBundles,
copyJsFilesToPL
)(done);
}
return gulp.series(
readProjectConfiguration,
gulp.parallel(
lintJS,
transpile,
livesiteJS,
processMarkup,
processAssets
),
writeBundles,
copyJsFilesToPL
)(done);
}
export function buildDocs(done) {
return gulp.series(
readProjectConfiguration,
processDocs
)(done);
}
export function buildAll(done) {
// TODO : review if --dev still required, since au run --watch now works as expected
if (CLIOptions.hasFlag('js') || CLIOptions.hasFlag('dev')) {
return buildJs(done);
} else if (CLIOptions.hasFlag('docs')) {
return gulp.series(
readProjectConfiguration,
gulp.parallel(
processDocs,
transpile,
processMarkup,
processAssets
),
writeBundles
)(done);
} else if (CLIOptions.hasFlag('data')) {
return gulp.series(
readProjectConfiguration,
copyDataToPL
)(done);
}
return gulp.series(
cleanAssets,
readProjectConfiguration,
gulp.parallel(
processDocs,
lintJS,
transpile,
livesiteJS,
processMarkup,
processAssets
),
writeBundles,
copyJsFilesToPL,
copyDataToPL
)(done);
}
export default function buildDefault(done) {
return buildAll(done);
}
function readProjectConfiguration() {
return build.src(project);
}
function writeBundles() {
return build.dest();
}

View File

@ -0,0 +1,26 @@
{
"name": "build",
"description": "Builds and processes all application assets.",
"flags": [
{
"name": "env",
"description": "Sets the build environment.",
"type": "string"
},
{
"name": "js",
"description": "Only build JS assets.",
"type": "string"
},
{
"name": "docs",
"description": "Only build documentation.",
"type": "string"
},
{
"name": "dev",
"description": "Only transpile JS and process markup. Do not use prior to committing.",
"type": "string"
}
]
}

View File

@ -0,0 +1,14 @@
/**
* Created by bstjohn on 01/04/2017.
*/
import gulp from 'gulp';
import debug from 'gulp-debug';
import project from '../aurelia.json';
import clean from 'gulp-clean';
export default function cleanAssets(done) {
let assets = project.assetsProcessor.clean;
return gulp.src(['assets/js/*'], {read: false})
.pipe(debug({title: `cleaning ${assets}`}))
.pipe(clean());
}

View File

@ -0,0 +1,12 @@
import gulp from 'gulp';
function copyDataToPL() {
let sourceDataFiles = ['test/data/**'];
let destinationData = '../pattern-library/public/data/';
return gulp.src(sourceDataFiles)
.pipe(gulp.dest(destinationData));
}
export default gulp.series(
copyDataToPL
);

View File

@ -0,0 +1,12 @@
import gulp from 'gulp';
function copyJsFilesToPL() {
let sourceJsFiles = [ 'assets/js/*.js', 'assets/js/*.map' ];
let destinationJs = '../pattern-library/public/assets/js/';
return gulp.src(sourceJsFiles)
.pipe(gulp.dest(destinationJs));
}
export default gulp.series(
copyJsFilesToPL
);

View File

@ -0,0 +1,26 @@
// linting configuration in is .eslintrc.json file in project root folder
import gulp from 'gulp';
//import project from '../aurelia.json';
import download from 'gulp-download';
import rename from 'gulp-rename';
import jsonFormat from 'gulp-json-format';
export default function getStubData() {
//let pageUrl = 'https://www.franklintempleton.com/en-us-retail/advisor/';
let pageUrl = 'http://rcovlnx0191:6205/en-us-retail/advisor/';
let fundId = '817';
let beans = [
'us.historical-cumulative-total-returns-labels',
'us.historical-after-tax-average-annual-total-returns-labels'
];
beans.forEach(bean => {
let url = fundId ? `${pageUrl}?FundID=${fundId}&bid=${bean}` : `${pageUrl}?bid=${bean}`;
let path = fundId ? `test/data/${fundId}` : 'test/data';
download(url)
.pipe(jsonFormat(2))
.pipe(rename(`${bean}.json`))
.pipe(gulp.dest(path));
});
}

View File

@ -0,0 +1,14 @@
// linting configuration in is .stylelintrc file in project root folder
import gulp from 'gulp';
import project from '../aurelia.json';
import stylelint from 'gulp-stylelint';
export default function lintCSS() {
return gulp.src(project.cssProcessor.source)
.pipe(stylelint({
reporters: [
{formatter: 'string', console: true}
]
}));
}

View File

@ -0,0 +1,45 @@
import gulp from 'gulp';
import project from '../aurelia.json';
import eslint from 'gulp-eslint';
import {CLIOptions} from 'aurelia-cli';
// linting configuration in is .eslintrc.json file in project root folder
function lintJsSrc() {
if (CLIOptions.hasFlag('watch')) {
return gulp.src(project.jsLinter.source)
.pipe(eslint())
.pipe(eslint.format());
}
return gulp.src(project.jsLinter.source)
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
}
// linting tests is les strict - console.log and
function lintJsTests() {
if (CLIOptions.hasFlag('watch')) {
return gulp.src(project.jsLinter.testSource)
.pipe(eslint({
'rules': {
'no-console': ['warn'],
'no-unused-vars': ['warn'],
'quotes': ['off', 'single']
}
}))
.pipe(eslint.format());
}
return gulp.src(project.jsLinter.testSource)
.pipe(eslint({
'rules': {
'no-console': ['warn'],
'no-unused-vars': ['warn'],
'quotes': ['off', 'single']
}
}))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
}
export default gulp.parallel(lintJsSrc, lintJsTests);

View File

@ -0,0 +1,23 @@
// linting configuration in is .eslintrc.json file in project root folder
import gulp from 'gulp';
import project from '../aurelia.json';
import lintspaces from 'gulp-lintspaces';
export default function lintMarkup() {
return gulp.src(project.markupProcessor.source)
.pipe(lintspaces({
'newline': true,
'newlineMaximum': 2,
'trailingspaces': true,
'indentation': 'spaces',
'spaces': 4,
'ignores': [
'html-comments'
]
}))
.pipe(lintspaces.reporter({
breakOnWarning: true,
prefixLogs: true
}));
}

View File

@ -0,0 +1,49 @@
import gulp from 'gulp';
import concat from 'gulp-concat';
import sourcemaps from 'gulp-sourcemaps';
import { CLIOptions } from 'aurelia-cli';
import uglify from 'gulp-uglify';
// import rename from 'gulp-rename';
import project from '../aurelia.json';
import babel from 'gulp-babel';
/**
* Builds LiveSite JS script
* @returns
*/
function buildJavaScriptLS() {
let environment = CLIOptions.getEnvironment();
if ( environment === 'dev' ) {
return gulp.src( project.livesiteJS.source )
.pipe( sourcemaps.init() )
.pipe( babel( project.livesiteJS.options ) )
.pipe( concat( 'livesite-modules.js' ) )
.pipe( sourcemaps.write( '.' ) )
.pipe( gulp.dest( project.livesiteJS.output ) );
} else if ( environment === 'stage' ) {
return gulp.src( project.livesiteJS.source )
.pipe( sourcemaps.init() )
.pipe( babel( project.livesiteJS.options ) )
.pipe( concat( 'livesite-modules.js' ) )
.pipe( uglify() )
// .pipe( rename( {
// suffix: '.min'
// } ) )
.pipe( sourcemaps.write( '.' ) )
.pipe( gulp.dest( project.livesiteJS.output ) );
} else if ( environment === 'prod' ) {
return gulp.src( project.livesiteJS.source )
.pipe( sourcemaps.init() )
.pipe( babel( project.livesiteJS.options ) )
.pipe( concat( 'livesite-modules.js' ) )
.pipe( uglify() )
// .pipe( rename( {
// suffix: '.min'
// } ) )
.pipe( gulp.dest( project.livesiteJS.output ) );
}
}
export default gulp.series(
buildJavaScriptLS
);

View File

@ -0,0 +1,9 @@
import gulp from 'gulp';
import zip from 'gulp-zip';
import project from '../aurelia.json';
export default function() {
return gulp.src(project.packager.src, {base: '.'})
.pipe(zip(project.packager.zipfile))
.pipe(gulp.dest(project.packager.outputDir));
}

View File

@ -0,0 +1,17 @@
import gulp from 'gulp';
// import changedInPlace from 'gulp-changed-in-place';
import project from '../aurelia.json';
// import {build} from 'aurelia-cli';
import debug from 'gulp-debug';
export default function processAssets(done) {
let subtasks = project.assetsProcessor.assets.map(asset => {
return function() {
return gulp.src(asset.src)
.pipe(debug({title: `copy ${asset.src} >>> ${asset.dest}`}))
.pipe(gulp.dest(asset.dest));
};
});
return gulp.parallel(subtasks)(done);
}

View File

@ -0,0 +1,32 @@
// changed to SASS+autoprefixer using this blog post: http://ilikekillnerds.com/2016/07/adding-postcssautoprefixer-aurelia-cli/
import gulp from 'gulp';
import sourcemaps from 'gulp-sourcemaps';
import project from '../aurelia.json';
import sass from 'gulp-sass';
import postcss from 'gulp-postcss';
import cssnano from 'gulp-cssnano';
import autoprefixer from 'autoprefixer';
import lintCSS from './lint-css';
import rename from 'gulp-rename';
export default function processCSS() {
let processors = [
autoprefixer(project.cssProcessor.browsers)
];
return gulp.src(project.cssProcessor.source)
.pipe(lintCSS())
.pipe(sourcemaps.init())
.pipe(sass({
includePaths: project.cssProcessor.sassIncludePaths
}).on('error', sass.logError))
.pipe(postcss(processors))
.pipe(cssnano())
.pipe(sourcemaps.write('.'))
.pipe(rename(path => {
// this means the generated css files are placed directly in assets/css folder instead of a scss sub-folder
path.dirname = path.dirname.replace('scss', '');
}))
.pipe(gulp.dest(project.cssProcessor.output));
}

View File

@ -0,0 +1,120 @@
import gulp from 'gulp';
import {CLIOptions} from 'aurelia-cli';
import project from '../aurelia.json';
import debug from 'gulp-debug';
import gutil from 'gulp-util';
import through from 'through2';
import File from 'vinyl';
import marked from 'marked';
import hljs from 'highlight.js';
export default function processDocs(done) {
if (CLIOptions.getEnvironment() !== 'prod') {
return gulp.src(project.docsProcessor.source)
.pipe(debug({title: 'generate documentation:'}))
.pipe(mdToJson(project.docsProcessor.jsonFile))
.pipe(gulp.dest(project.docsProcessor.outputDir));
}
return null;
}
// set up document index - matches docs to sections based on regex match on their paths
let docs = project.docsProcessor.index;
docs.pages = {};
let regexes = new Map();
function getRegexes(section) {
if (section.regex !== undefined) {
section.pages = [];
regexes.set(new RegExp(section.regex), section.pages);
}
if (section.subs !== undefined) {
section.subs.forEach(getRegexes);
}
}
docs.index.forEach(getRegexes);
let uncategorizedPages = docs.index.find(section => section.section === 'Uncategorized');
uncategorizedPages = uncategorizedPages.pages = [];
// need a bespoke renderer for marked to put the hljs tag on the code element
// might get fixed in subsequent release
let myRenderer = new marked.Renderer();
if (typeof hljs !== 'undefined') {
myRenderer.code = function(code, lang, escaped) {
if (lang && hljs.getLanguage(lang)) {
try {
code = hljs.highlight(lang, code).value;
} catch (e) {
// fail silently
}
}
return '<pre><code'
+ (lang
? ' class="hljs ' + this.options.langPrefix + lang + '"'
: ' class="hljs"')
+ '>'
+ code
+ '\n</code></pre>\n';
};
}
marked.setOptions({
renderer: myRenderer,
highlight: function(code) {
return hljs.highlightAuto(code).value;
},
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false
});
function mdToJson(out) {
return through.obj(function(file, enc, cb) {
if (file.isNull()) {
cb(null, file);
return;
}
if (file.isStream()) {
cb(new gutil.PluginError('mdToJson', 'Streams not supported'));
return;
}
let fileid = file.relative.replace(/\\/g, '/');
docs.pages[fileid] = marked(file.contents.toString('utf8'));
// try and match to correct section
let uncategorized = true;
for (let [regex, pages] of regexes) {
if (regex.test(fileid)) {
if (! pages.some( id => id === fileid)) {
pages.push(fileid);
}
uncategorized = false;
break;
}
}
if (uncategorized) {
if (! uncategorizedPages.some( id => id === fileid)) {
uncategorizedPages.push(fileid);
}
}
cb();
}, function(cb) {
let buffer = new Buffer(JSON.stringify(docs, null, ' '));
let fileListFile = new File({
path: out,
contents: buffer
});
this.push(fileListFile);
cb();
});
}

View File

@ -0,0 +1,20 @@
import gulp from 'gulp';
import htmlmin from 'gulp-htmlmin';
import changedInPlace from 'gulp-changed-in-place';
import project from '../aurelia.json';
import {build} from 'aurelia-cli';
import lintMarkup from './lint-markup';
export default function processMarkup() {
return gulp.src(project.markupProcessor.source)
.pipe(lintMarkup())
.pipe(changedInPlace({firstPass: true}))
.pipe(htmlmin({
removeComments: true,
collapseWhitespace: true,
conservativeCollapse: true,
minifyCSS: true,
minifyJS: true
}))
.pipe(build.bundle());
}

View File

@ -0,0 +1,184 @@
import gutil from 'gulp-util';
import {CLIOptions} from 'aurelia-cli';
/**
* Example usage
* au proxy-server --static-cache --gzip --remove-tracking --override-aurelia-config configuration/etf/main --dev-js-assets-server http://localhost:9000/ --dev-css-assets-server http://pattern-library.corp.frk.com/
*/
const defaultUrl = CLIOptions.getFlagValue('proxy-target') ? CLIOptions.getFlagValue('proxy-target') : 'https://www.franklintempleton.com/';
// --proxy-target http://rcovlnx0188:8202
export function proxyServer(done) {
gutil.log('starting proxy server on http://localhost:2000 (proxying = ' + defaultUrl + ')');
if (CLIOptions.hasFlag('dev-js-assets-server')) {
gutil.log(' replace js assets with ' + CLIOptions.getFlagValue('dev-js-assets-server'));
}
if (CLIOptions.hasFlag('dev-css-assets-server')) {
gutil.log(' replace css assets with ' + CLIOptions.getFlagValue('dev-css-assets-server'));
}
let express = require('express');
let proxy = require('express-http-proxy');
let cache = require('express-cache-response');
let compression = require('compression');
let app = express();
if (CLIOptions.hasFlag('static-cache')) {
app.use(cache());
}
if (CLIOptions.hasFlag('gzip')) {
app.use(compression());
}
if (CLIOptions.hasFlag('dev-js-assets-server')) {
let devAssetsServer = CLIOptions.getFlagValue('dev-js-assets-server').replace(/\/$/, '');
app.use('/assets/js/', proxy(devAssetsServer + '/assets/js/', {
changeOrigin: true,
/**
* You can mutate the request options before sending the proxyRequest.
* proxyReqOpt represents the options argument passed to the (http|https).request module.
*
* @param proxyReqOpts
* @param srcReq
* @returns {*}
*/
proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
proxyReqOpts.headers['Cache-Control'] = 'no-cache';
proxyReqOpts.headers['Cache-Control'] = 'no-store';
proxyReqOpts.headers['Cache-Control'] = 'no-transform';
proxyReqOpts.headers['Cache-Control'] = 'only-if-cached';
proxyReqOpts.headers['ETag'] = 'ruin_the_etag' + Math.random();
return proxyReqOpts;
},
/**
* Provide a proxyReqPathResolver function if you'd like to operate on the path before issuing the proxy request.
* Use a Promise for async operations.
* @param req
*/
proxyReqPathResolver: function(req) {
return '/assets/js' + require('url').parse(req.url).path;
},
/**
* You can modify the proxy's response before sending it to the client.
* @param proxyRes
* @param proxyResData
* @param userReq
* @param userRes
*/
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
let isJS = proxyRes.headers['content-type'] ? proxyRes.headers['content-type'].indexOf('text/javascript') !== -1 : false;
isJS = isJS || proxyRes.headers['content-type'] ? proxyRes.headers['content-type'].indexOf('application/javascript') !== -1 : false;
isJS = isJS || userReq.originalUrl.endsWith('.js');
gutil.log(' > proxying dev js (' + userReq.originalUrl + ') from : ' + devAssetsServer + userReq.originalUrl);
proxyRes.headers['etag'] = 'ruin_the_etag' + Math.random();
proxyResData = proxyResData.toString('utf8');
return proxyResData;
}
}));
}
app.use('/', proxy(defaultUrl, {
changeOrigin: true,
/**
* You can modify the proxy's response before sending it to the client.
* @param proxyRes
* @param proxyResData
* @param userReq
* @param userRes
*/
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
let isHtml = proxyRes.headers['content-type'] ? proxyRes.headers['content-type'].indexOf('text/html') !== -1 : false;
let isJS = proxyRes.headers['content-type'] ? proxyRes.headers['content-type'].indexOf('text/javascript') !== -1 : false;
isJS = isJS || proxyRes.headers['content-type'] ? proxyRes.headers['content-type'].indexOf('application/javascript') !== -1 : false;
isJS = isJS || userReq.originalUrl.endsWith('.js');
if (isHtml) {
gutil.log(' > rewriting : ' + userReq.originalUrl);
proxyResData = proxyResData.toString('utf8');
if (CLIOptions.hasFlag('remove-tracking')) {
// remove Google Tag Manager
proxyResData = proxyResData.replace(/<!-- Google Tag Manager -->(.|[\r\n])*<!-- End Google Tag Manager -->/gm, function(replace) {
gutil.log(' >> removing string : ' + replace);
return '';
});
// remove s7
proxyResData = proxyResData.replace(/<script[^>]+src="https:\/\/s7.addthis.com\/.*"[^>]*><\/script>/gm, function(replace) {
gutil.log(' >> removing string : ' + replace);
return '';
});
}
if (CLIOptions.hasFlag('remove-tenant-libs')) {
proxyResData = proxyResData.replace(/<script[^>]+src=".*\/((main-home)|(handlebars.*))\.js"[^>]*><\/script>/gm, function(replace) {
gutil.log(' >> removing string tenant libraries: ' + replace);
return '';
});
}
if (CLIOptions.hasFlag('remove-images')) {
// kill images by redirecting to favicon
proxyResData = proxyResData.replace(/.*\.(jpeg|jpg|png)/gm, function(replace) {
gutil.log(' >> removing image: ' + replace);
return '/web-resources/ui/default/images/favicon.ico';
});
}
if (CLIOptions.hasFlag('dev-css-assets-server')) {
let devAssetsServer = CLIOptions.getFlagValue('dev-css-assets-server');
// redirect to other host
proxyResData = proxyResData.replace(/\/assets\/css\/core(\.min)?\.css/gm, function(replace) {
let cssUrl = devAssetsServer + '/assets/css/core.css';
gutil.log(' >> replacing ' + replace + ' for link to remote pattern-library (' + cssUrl + ')');
return cssUrl;
});
}
if (CLIOptions.getFlagValue('override-aurelia-config')) {
let aureliaApp = CLIOptions.getFlagValue('override-aurelia-config');
proxyResData = proxyResData.replace(/aurelia-app=".*"/gm, function(replace) {
gutil.log(' >> replacing aurelia app ' + replace + ' for : ' + aureliaApp);
return 'aurelia-app="' + aureliaApp + '"';
});
}
// Inject Timing Code
// Use console.timeEnd("from-page-load"); to get time from page load
proxyResData = proxyResData.replace(/head>/gm, function(replace) {
return 'head><script>console.time("from-page-load");</script>';
});
}
if (isJS) {
proxyResData = proxyResData.toString('utf8');
}
return proxyResData;
},
/**
* You can mutate the body content before sending the proxyRequest.
* @param bodyContent
* @param srcReq
* @returns {*}
*/
proxyReqBodyDecorator: function(bodyContent, srcReq) {
return bodyContent;
},
/**
* You can mutate the request options before sending the proxyRequest.
* proxyReqOpt represents the options argument passed to the (http|https).request module.
*
* @param proxyReqOpts
* @param srcReq
* @returns {*}
*/
proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
return proxyReqOpts;
}
}));
app.listen(2000);
}
export default function proxyDefault(done) {
proxyServer(done);
}

View File

@ -0,0 +1,53 @@
{
"name": "proxy-server",
"description": "Proxies main site with dev application assets.",
"flags": [
{
"name": "override-aurelia-config",
"description": "eg configuration/etf/main",
"type": "string"
},
{
"name": "proxy-target",
"description": "server to proxy eg http://rcovlnx0188:8202",
"type": "string"
},
{
"name": "dev-js-assets-server",
"description": "Use hosted JS files on another server eg http://localhost:9000",
"type": "string"
},
{
"name": "dev-css-assets-server",
"description": "Use hosted JS files on another server eg http://localhost:9000 (will default to unmin-ed version)",
"type": "string"
},
{
"name": "remove-tracking",
"description": "Remove all google and misc tracking code",
"type": "boolean"
},
{
"name": "remove-images",
"description": "Remove all images, replace them with 1 pixel equivilients",
"type": "boolean"
},
{
"name": "remove-tenant-libs",
"description": "Remove tenant libs",
"type": "boolean"
},
{
"name": "gzip",
"description": "gzip asset",
"type": "boolean"
},
{
"name": "static-cache",
"description": "cache asset, dumb cache never expires",
"type": "boolean"
}
]
}

View File

@ -0,0 +1,189 @@
import gulp from 'gulp';
import gutil from 'gulp-util';
import browserSync from 'browser-sync';
import historyApiFallback from 'connect-history-api-fallback/lib';
import project from '../aurelia.json';
import {buildAll, buildJs, buildDocs} from './build';
import {CLIOptions} from 'aurelia-cli';
import request from 'request';
function onChange(path) {
gutil.log(`File Changed: ${path}`);
}
function reload(done) {
browserSync.reload();
done();
}
let serve = gulp.series(
buildAll,
done => {
browserSync({
online: false,
open: false,
port: 9000,
logLevel: 'info',
server: {
baseDir: ['.'],
middleware: [
relaxAllowOrigin,
proxyFixtures,
historyApiFallback()
]
}
}, function(err, bs) {
let urls = bs.options.get('urls').toJS();
gutil.log(`Application Available At: ${urls.local}`);
gutil.log(`BrowserSync Available At: ${urls.ui}`);
done();
});
}
);
let relaxAllowOrigin = function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
};
let proxyFixtures = function(req, res, next) {
// rest api rewrites
let isLocalMode = !CLIOptions.hasFlag('use-test-server');
if (isLocalMode) {
proxyLocalFixtures(req, res, next);
} else {
proxyRemoteFixtures(req, res, next);
}
};
/**
* proxies data feed into server
* eg
* http://localhost:9000/etf-gb/investor/data/21412/etf.fund-details
* proxied to
* http://rcovlnx0192:8322/etf-gb/investor/data/21412/etf.fund-details
*
* @param req
* @param res
* @param next
*/
let proxyRemoteFixtures = function(req, res, next) {
let testFundId = CLIOptions.getFlagValue('test-server-fund-id') || '21412';
let testServer = CLIOptions.getFlagValue('test-server') || 'http://rcovlnx0192:8322';
let testSite = CLIOptions.getFlagValue('test-site') || false;
let proxyUrl;
let matches = req.url.match(/^\/([^/]+)\/([^/]+)\/data\/(\d+)\/(.*)/);
if (matches !== null) {
// gutil.log(`site=${matches[1]}, segment=${matches[2]}, fundId=${matches[3]}, beanId=${matches[4]}`);
let site = testSite ? testSite : matches[1];
proxyUrl = `${testServer}/${site}/${matches[2]}/data/${testFundId}/${matches[4]}`;
} else {
matches = req.url.match(/^\/([^/]+)\/([^/]+)\/data\/(.*)/);
if (matches !== null) {
// gutil.log(`site=${matches[1]}, segment=${matches[2]}, beanId=${matches[3]}`);
let site = testSite ? testSite : matches[1];
proxyUrl = `${testServer}/${site}/${matches[2]}/data/${matches[3]}`;
}
}
if (proxyUrl) {
// any small bean changes add here
// eg
// proxyUrl = proxyUrl.replace('etf.fund-details', 'etf.fund-details');
// proxyUrl = proxyUrl.replace('etf.distribution-history', 'etf.distribution-history');
gutil.log(`${req.url} -> ${proxyUrl}`);
request.get(
proxyUrl, {},
function(error, response, contents) {
// gutil.log(error, response, body);
if (!error && response.statusCode === 200) {
try {
if (!contents.match(/^( |[\r\n])*$/)) {
let json = JSON.parse(contents);
json.__debug = {
injectedByNode: true,
sourceUrl: proxyUrl
};
contents = JSON.stringify(json, null, 2);
}
} catch (e) {
gutil.log(e);
}
res.end(contents);
} else {
gutil.log(error);
}
next();
}
);
} else {
gutil.log(req.url);
next();
}
};
/**
* @param req
* @param res
* @param next
*/
let proxyLocalFixtures = function(req, res, next) {
let matches = req.url.match(/^\/([^/]+)\/([^/]+)\/data\/(\d+)\/(.*)/);
if (matches !== null) {
// gutil.log(`site=${matches[1]}, segment=${matches[2]}, fundId=${matches[3]}, beanId=${matches[4]}`);
let fixtureFile = `/test/data/${matches[1]}/${matches[3]}/${matches[4]}.json`;
gutil.log(`${req.url} -> ${fixtureFile}`);
req.url = fixtureFile;
} else {
matches = req.url.match(/^\/([^/]+)\/([^/]+)\/data\/(.*)/);
if (matches !== null) {
// gutil.log(`site=${matches[1]}, segment=${matches[2]}, beanId=${matches[3]}`);
let fixtureFile = `/test/data/${matches[1]}/${matches[3]}.json`;
gutil.log(`${req.url} -> ${fixtureFile}`);
req.url = fixtureFile;
} else {
gutil.log(req.url);
}
}
next();
};
let refresh = gulp.series(
buildAll,
reload
);
let refreshJs = gulp.series(
buildJs,
reload
);
let refreshDocs = gulp.series(
buildDocs,
reload
);
let watch = function() {
// aurelia bundles
gulp.watch(project.transpiler.source, refreshJs).on('change', onChange);
gulp.watch(project.markupProcessor.source, refreshJs).on('change', onChange);
// markdown pages
gulp.watch(project.docsProcessor.source, refreshDocs).on('change', onChange);
gulp.watch(project.cssProcessor.source, refresh).on('change', onChange);
};
let run;
if (CLIOptions.hasFlag('watch')) {
run = gulp.series(
serve,
watch
);
} else {
run = serve;
}
export default run;

View File

@ -0,0 +1,36 @@
{
"name": "run",
"description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.",
"flags": [
{
"name": "env",
"description": "Sets the build environment.",
"type": "string"
},
{
"name": "watch",
"description": "Watches source files for changes and refreshes the app automatically.",
"type": "boolean"
},
{
"name": "use-test-server",
"description": "Get Data from test server, default fund id 21412, default server http://rcovlnx0192:8322.",
"type": "boolean"
},
{
"name": "test-server",
"description": "Set Data from test server, requires --use-test-server.",
"type": "string"
},
{
"name": "test-server-fund-id",
"description": "Set Test Server to use, requires --use-test-server.",
"type": "string"
},
{
"name": "test-site",
"description": "Set Test Site to use eg etf-en or etf-us, requires --use-test-server.",
"type": "string"
}
]
}

View File

@ -0,0 +1,41 @@
import gulp from 'gulp';
import project from '../aurelia.json';
import sonar from 'gulp-sonar';
export default function sonarTest( done ) {
let version = '0.1.0';
let projectKey = 'sonar:' + project.name + ':' + version;
let options = {
sonar: {
host: {
url: project.sonar.sonarURL
},
jdbc: {
url: project.sonar.sonarDB,
username: project.sonar.sonarUser,
password: project.sonar.sonarPasswd
},
projectKey: projectKey,
projectName: project.name,
projectVersion: version,
// comma-delimited string of source directories
sources: project.sonar.sources,
exclusions: project.sonar.exclusions,
language: 'js',
sourceEncoding: 'UTF-8',
javascript: {
lcov: {
reportPath: project.sonar.reportPath
}
},
exec: {
// All these properties will be send to the child_process.exec method (see: https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback )
// Increase the amount of data allowed on stdout or stderr (if this value is exceeded then the child process is killed, and the gulp-sonar will fail).
maxBuffer: 1024 * 1024
}
}
};
// gulp source doesn't matter, all files are referenced in options object above
return gulp.src( 'src/**/*.js', { read: false } )
.pipe( sonar( options ) );
}

View File

@ -0,0 +1,45 @@
import gulp from 'gulp';
import {Server as Karma} from 'karma';
import {CLIOptions} from 'aurelia-cli';
import jest from 'gulp-jest';
// TODO: add coverage options
function testUI(done) {
let karamConfig = {
configFile: __dirname + '/../../karma.conf.js'
};
if (CLIOptions.hasFlag('watch')) {
karamConfig.singleRun = false;
karamConfig.specReporter = {
showSpecTiming: true,
suppressSkipped: true,
suppressErrorSummary: false
};
}
new Karma(karamConfig, done).start();
}
function testBusinessLogic() {
return gulp.src('src').pipe(jest({
config: {
'verbose': true,
'testRegex': '(util|lib|value-converters).*.spec.js$',
'collectCoverage': true,
'coverageDirectory': '../coverage',
'coverageReporters': ['html', 'clover']
}
}));
}
export default function test(done) {
if (CLIOptions.hasFlag('bl')) {
return testBusinessLogic();
} else if (CLIOptions.hasFlag('ui')) {
return testUI(done);
}
return gulp.series(
testBusinessLogic,
testUI
)();
}

View File

@ -0,0 +1,26 @@
{
"name": "test",
"description": "Runs all unit tests and reports the results.",
"flags": [
{
"name": "env",
"description": "Sets the build environment.",
"type": "string"
},
{
"name": "watch",
"description": "Watches test files for changes and re-runs the tests automatically.",
"type": "boolean"
},
{
"name": "bl",
"description": "Test only the Business Logic (non-UI e.g. reducers) code via Jest.",
"type": "boolean"
},
{
"name": "ui",
"description": "Test only the UI components via Karma.",
"type": "boolean"
}
]
}

View File

@ -0,0 +1,35 @@
import gulp from 'gulp';
import changedInPlace from 'gulp-changed-in-place';
import plumber from 'gulp-plumber';
import babel from 'gulp-babel';
import sourcemaps from 'gulp-sourcemaps';
import notify from 'gulp-notify';
import rename from 'gulp-rename';
import project from '../aurelia.json';
import {CLIOptions, build} from 'aurelia-cli';
// import debug from 'gulp-debug';
function configureEnvironment() {
let env = CLIOptions.getEnvironment();
return gulp.src(`aurelia_project/environments/${env}.js`)
.pipe(changedInPlace({firstPass: true}))
.pipe(rename('environment.js'))
.pipe(gulp.dest(project.paths.root));
}
function buildJavaScript() {
// TODO: should lintJS run here instead of in the build task?
return gulp.src(project.transpiler.source)
// .pipe(debug({title: 'start transpile:'}))
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
.pipe(changedInPlace({firstPass: true}))
.pipe(sourcemaps.init())
.pipe(babel(project.transpiler.options))
.pipe(build.bundle());
}
export default gulp.series(
configureEnvironment,
buildJavaScript
);

29
docs.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Site Configuration</title>
<link rel="stylesheet" href="http://pattern-library.corp.frk.com/assets/css/core.css" type="text/css"/> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/styles/kimbie.dark.min.css">
<style>
code {
text-transform: none;
}
a {
cursor: pointer;
}
p code {
color: #a0a0a0;
padding: 1px;
}
li code {
color: #a0a0a0;
}
</style>
</head>
<body aurelia-app="components/test/docs/main">
<script src="/assets/js/web-platform.js" data-main="aurelia-bootstrapper"></script>
</body>
</html>

BIN
favicon.ico Normal file

Binary file not shown.

6434
geocities.css Normal file

File diff suppressed because it is too large Load Diff

109
index.html Normal file
View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html>
<head>
<title>Web Platform Local Development Environment</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/muicss/0.9.12/css/mui.min.css" type="text/css"/>
</head>
<body>
<div class="mui-container">
<h1 class="rainbow">Web Platform Local Development Environment</h1>
<div class="mui-panel">
<h2>Documentation</h2>
<a href="/docs.html">Docs</a>
</div>
<div class="mui-panel">
<h2>Unit test coverage</h2>
<a href="/coverage/index.html">Reducers</a>
<br>
<a href="/coverage/components/index.html">Components</a>
</div>
<div class="mui-panel">
<h2>UK ETF</h2>
<a href="/test/pages/etf-gb/fund-explorer.html">ETF Fund Explorer</a>
<br>
<a href="/test/pages/etf-gb/overview.html">ETF Overview</a>
<br/>
<a href="/test/pages/etf-gb/fund-trading-characteristics.html">ETF Fund Trading Characteristics</a>
<br/>
<a href="/test/pages/etf-gb/portfolio.html">ETF Portfolio</a>
<br/>
<a href="/test/pages/etf-gb/portfolio-21412.html">ETF Portfolio 21412</a>
<br>
<a href="/test/pages/etf-gb/performance.html">ETF Performance</a>
<br>
<a href="/test/pages/etf-gb/performance-annualised.html">ETF Performance Annualised</a>
<br>
<a href="/test/pages/etf-gb/calendar-year.html">ETF Performance Calendar</a>
<br>
<a href="/test/pages/etf-gb/backtested-performance.html">ETF Backtested Performances</a>
<br>
<a href="/test/pages/etf-gb/pricing-distribution.html">ETF Pricing & Distribution</a>
</div>
<div class="mui-panel">
<h2>UK Test Pages</h2>
<a href="/test/pages/en-gb/carousel.html">Carousels</a>
<br>
<a href="/test/pages/en-gb/grid-filter.html">Grid Filter</a>
<br>
<a href="/test/pages/en-gb/performance.html">Performance</a>
<br>
<a href="/test/pages/en-gb/portfolio.html">Portfolio</a>
<br>
<a href="/test/pages/en-gb/ppss.html">PPSS</a>
<br>
<a href="/test/pages/en-gb/pricing-distribution.html">Pricing & Distribution</a>
<br>
<a href="/test/pages/en-gb/video-switch.html">Video Switch</a>
<br>
<br>
<a href="/test/pages/en-gb/marketing.html">Document Archives</a>
<br/>
<a href="/test/pages/en-gb/doclister.html">Document Lister</a>
<br/>
<a href="/test/pages/en-gb/stickytabs.html">Sticky Tabs</a>
<br/>
<a href="/test/pages/en-gb/overview.html">Overview</a>
</div>
<div class="mui-panel">
<h2>US Test Pages</h2>
<a href="/test/pages/en-us-retail/portfolio4.html">Portfolio <sup><strong>NEWEST</strong></sup></a>
<br>
<a href="/test/pages/en-us-retail/performance2.html">Performance</a>
<br>
<a href="/test/pages/en-us-retail/pricing2.html">Pricing</a>
<br>
<a href="/test/pages/en-us-retail/product-landing-page.html">Product Landing</a>
<br>
<a href="/test/pages/en-us-retail/tax-information2.html">Tax Information</a>
<br>
<a href="/test/pages/en-us-retail/ppss.html">PPSS</a>
<br>
<a href="/test/pages/en-us-retail/historical-average-annual-total-returns-placeholder.html">Historical AATR Modal Placeholder</a>
<br>
<a href="/test/pages/en-us-retail/historical-cumulative-total-returns-placeholder.html">Historical CTR Modal Placeholder</a>
<h2>US Broken Test Pages</h2>
<a href="/test/pages/en-us-retail/distributions2.html">US Distributions</a>
<br>
<a href="/test/pages/en-us-retail/overview2.html">Overview</a>
<br>
<a href="/test/pages/en-us-retail/portfolio2.html">Portfolio <sup>NEW</sup></a>
<br>
<a href="/test/pages/en-us-retail/portfolio3.html">Portfolio <sup><strong>NEWER</strong></sup></a>
<br>
<a href="/test/pages/en-us-retail/documents.html">Documents</a>
<br>
<a href="/test/reference/gateway/gatewayPage.html">Gateway</a>
<br>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,5 @@
{
"verbose": true,
"rootDir": "src/components/products/portfolio",
"testRegex": "(util|lib|value-converters).*.spec.js$"
}

5
jest-ppss-reducers.json Normal file
View File

@ -0,0 +1,5 @@
{
"verbose": true,
"rootDir": "src/components/products/ppss/ft-ppss",
"testRegex": "(util|lib|value-converters).*.spec.js$"
}

5
jest-reducers.json Normal file
View File

@ -0,0 +1,5 @@
{
"verbose": true,
"rootDir": "src",
"testRegex": "(util|lib|value-converters).*.spec.js$"
}

12
jsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "ES6"
},
"exclude": [
"assets",
"coverage",
"node_modules",
"ui-toolkit"
]
}

68
karma.conf.js Normal file
View File

@ -0,0 +1,68 @@
'use strict';
const path = require('path');
const project = require('./aurelia_project/aurelia.json');
let testSrc = [
{ pattern: project.unitTestRunner.source, included: false },
{ pattern: 'test/unit/setup.js', included: false },
{ pattern: 'src/**/*.fixture.json', included: false },
'test/aurelia-karma.js'
];
let output = project.platform.output;
let appSrc = project.build.bundles.map(x => path.join(output, x.name));
let entryIndex = appSrc.indexOf(path.join(output, project.build.loader.configTarget));
let entryBundle = appSrc.splice(entryIndex, 1)[0];
let files = [entryBundle].concat(testSrc).concat(appSrc);
module.exports = function(config) {
config.set({
basePath: '',
frameworks: [project.testFramework.id],
files: files,
exclude: [
'src/**/lib/**/*.spec.js',
'src/**/util/**/*.spec.js',
'src/**/value-converters/**/*.spec.js'
],
preprocessors: {
[project.unitTestRunner.source]: [project.transpiler.id],
['test/unit/setup.js']: [project.transpiler.id],
'src/**/!(*.spec)+(.js)': ['coverage']
},
'babelPreprocessor': { options: project.transpiler.options },
reporters: ['coverage', 'spec'],
coverageReporter: {
type: 'html',
dir: 'coverage/',
subdir: 'components',
includeAllSources: true
},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
// browsers: ['PhantomJS'],
browsers: ['Chrome'],
// browsers: ['ChromeNoSandboxHeadless'], // headless Chrome should work from v59+
customLaunchers: {
ChromeNoSandboxHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
// https://www.chromestatus.com/features/5678767817097216
'--headless',
'--disable-gpu',
// Without a remote debugging port, Google Chrome exits immediately.
' --remote-debugging-port=9222'
]
}
},
singleRun: true,
phantomjsLauncher: {
// Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom)
exitOnResourceError: true
}
});
};

1
labels-array.json Normal file
View File

@ -0,0 +1 @@
["1-yr","10-Hypo-text-Bottom","10-Hypo-text-Top","10-yr","10yr","15-yr","1yr","250k-499k","3-yr","3yr","5-yr","500k-999k","50k-99k","52-week-range-heading","5yr","7-day-current-yield","7-day-effective-yield","actual-allocation","actual-allocation-type","add-to-factsheet-to-cart","add-to-favourite.add","additional-tax-information","advantage-hypo-para","after-shares-sold","all","all-fund-distribution-message","allocation","amount","AS_OF_DATE","as-of","asset-class-exposure-calculation-type","assetclass","at-nav","ataatr-info-text","aum","before-shares-sold","below-investment-grade","calendar-year-1yr","calendar-year-2yr","calendar-year-3yr","calendar-year-4yr","calendar-year-5yr","calendar-year-6yr","capital-gain-distributions","cash-and-cash-equv","category","chart","chart-alternate","close","compare-selected-funds","component-heading","component-sub-heading","component-subheading","component-subheading-sector-exposure","contact-us-info","cumulative-performance-heading","currency","daily-fund-prices","date-selector-info","direct-usgovt-obligations","discrete-1yr","discrete-2yr","discrete-3yr","discrete-4yr","discrete-5yr","distribution-date","distribution-rate-and-breakpoints","distributions","distributions-estimates","dividend-distributions","dividends","dividends-per-share","dividends-received-deduction","domicile","download","download-all-holdings","etf","ex-date","ex-dividend-date","expenses-collapse","expenses-collapse.cdsc","expenses-collapse.gross","expenses-collapse.max","expenses-collapse.net","expenses-collapse.ratio","expenses-collapse.sales-charges","factsheet","fav","favouriteonly","federal-id-number","find-a-fund","flag.hard-closure","flag.soft-closure","for-tax-year","foreign-tax-credit-information-label","fund-identifiers","fund-information","fund-name","fund-number","fund-with-sales-charge","fund-without-sales-charge","fundname-shareclass-seperator","funds-selected","further-information","geographic-exposure-calculation-type","gross-exposure-calculation-type","gross-exposure-coloum","heading","highest-lowest-nav","highest-nav","historical-data-link-text","holdings","hypothetical-chart-title","important-fund-info","inception","inception-date","income-use","info-collapse-content","info-modal-content","info-modal-content-2","info-modal-content-3","investment","investment-grade","isin","issuers","less-than-50k","life","long-term-cap-gains-label","lowest-nav","manager-allocation-calc-type","market-value","mdash","millions-indicator","money-funds","month","month-end","morningstar-category","morningstar-rating","morningstar.not-rated","morningstar.rating-too-low","name","nav","NAV","NAV-as-of-post","NAV-as-of-pre","nav-change","NAV-change","NAV-high","NAV-low","NAV-sub","nav-total-return","nb-funds-in-category","net-exposure-calculation-type","net-exposure-coloum","next","no-data-msg","no-distribution-paid-message","no-distributions","no-managers-message","no-results-message","non-tax-free-income-fund-para","non-taxable-return-of-capital-label","not-applicable-message","not-rated","notional-value","of","off","on","ongoing-charges","ordinary-dividends-info-text","ordinary-dividends-label","other","overall","pay-date-label","payable-date","performance-annualised-heading","performance-tab","POP","POP-sales-charge-heading","POP-sub","positions-heading","positions-not-available","previous","qualified-dividend-income-info-text","qualified-dividend-income-label","quarter-end","ratings-tab","read-important","record-date","record-date-label","refunded","reinvestment-date","reinvestment-price","reset-to-current","sales-charge","salescharges","schedules","search-fields","sector","security-name","share-class","share-prices-heading","show-performance","showing","specified-private-activity-bond-interest","statistics-heading","subheading","symbol","table","table-heading","target-allocation","target-allocation-type","tax-exempt-income-jurisdiction-label","tax-exempt-income-label","tax-year","ter","ticker","total-distributions-label","total-label","updated-daily","updated-monthly","value","view-historical-data","weight","year","year-end-message","year-to-date-distributions-per-share","yield-tab","yield.w-waiver","yield.wo-waiver","ytd","ytd-total-ret"]

340
labels-object.json Normal file
View File

@ -0,0 +1,340 @@
{
"1-yr": [
"1 Yr",
"1 YR"
],
"10-Hypo-text-Bottom": [],
"10-Hypo-text-Top": [],
"10-yr": [
"10 Yr",
"10 YR"
],
"10yr": "10 YR",
"15-yr": [],
"1yr": "1 YR",
"250k-499k": [],
"3-yr": [
"3 Yr",
"3 YR"
],
"3yr": "3 YR",
"5-yr": [
"5 Yr",
"5 YR"
],
"500k-999k": [],
"50k-99k": [],
"52-week-range-heading": "52-week Range ($)",
"5yr": "5 YR",
"7-day-current-yield": "7 Day Current Yield",
"7-day-effective-yield": "7 Day Effective Yield",
"actual-allocation": "Actual Allocation",
"actual-allocation-type": [],
"add-to-factsheet-to-cart": "Add Fact Sheet to My Cart",
"add-to-favourite.add": "Add to my Favorites",
"additional-tax-information": "Additional Tax Information",
"advantage-hypo-para": "Create a client-use report of the fund's performance, tailored for a specific client.",
"after-shares-sold": "After Shares Sold",
"all": "All",
"all-fund-distribution-message": "All fund distributions will vary depending on current market conditions, and past distributions are not indicative of future trends.",
"allocation": [],
"amount": [],
"AS_OF_DATE": [],
"as-of": [
"As of",
"As of "
],
"asset-class-exposure-calculation-type": "% of Total asset exposure calculation-type",
"assetclass": "Asset Class",
"at-nav": "at NAV",
"ataatr-info-text": [],
"aum": [],
"before-shares-sold": "Before Shares Sold",
"below-investment-grade": "Below Investment Grade",
"calendar-year-1yr": "Year 1",
"calendar-year-2yr": "Year 2",
"calendar-year-3yr": "Year 3",
"calendar-year-4yr": "Year 4",
"calendar-year-5yr": "Year 5",
"calendar-year-6yr": "Year 6",
"capital-gain-distributions": [],
"cash-and-cash-equv": "Cash &amp; Cash Equivalents",
"category": [],
"chart": "Chart",
"chart-alternate": "No chart available for this data.",
"close": [
"Close",
"close"
],
"compare-selected-funds": "Compare Selected Funds",
"component-heading": [
"Average Annual Total Returns",
"Cumulative Total Returns",
"HISTORICAL DATA - After-Tax Average Annual Total Returns",
"HISTORICAL DATA - Average Annual Total Returns",
"HISTORICAL DATA - Cumulative Total Returns",
"Geographic Allocation",
"Market Capitalisation",
"Sector Allocation",
"Geographic Breakdown",
"Portfolio",
"Asset Class Exposure",
"Asset Allocation",
"Commodities Sector Breakdown",
"Coupon Rate",
"credit quality exposure",
"CURRENCY DISTRIBUTION",
"Daily Fund Prices and Breakpoints",
"Fixed Income Sector Breakdown",
"Geographic Exposure",
"Manager Roster",
"Maturity Breakdown",
"Pricing at",
"Quality Breakdown",
"sector breakdown",
"sector exposure",
"Strategy Breakdown",
"Tax Information",
"Top Five Holdings",
"TOP TEN HOLDINGS"
],
"component-sub-heading": [
"AFTER-TAX AVERAGE ANNUAL TOTAL RETURNS",
"Average Annual Total Returns",
"Cumulative Total Returns"
],
"component-subheading": [],
"component-subheading-sector-exposure": "sub heading",
"contact-us-info": "For information on tax years prior to 2008, please contact us.",
"cumulative-performance-heading": [],
"currency": "Currency",
"daily-fund-prices": [],
"date-selector-info": "Choose a month and year from the drop-menus above to show historical performance data.",
"direct-usgovt-obligations": "Direct U.S.Gov't Obligations %",
"discrete-1yr": "Dec-15 / Dec-16",
"discrete-2yr": "Dec-14 / Dec-15",
"discrete-3yr": "Dec-13 / Dec-14",
"discrete-4yr": "Dec-12 / Dec-13",
"discrete-5yr": "Dec-11 / Dec-12",
"distribution-date": [],
"distribution-rate-and-breakpoints": [],
"distributions": [
"Distributions",
"DISTRIBUTIONS (PER SHARE)"
],
"distributions-estimates": [],
"dividend-distributions": [],
"dividends": [],
"dividends-per-share": [],
"dividends-received-deduction": "Dividends-Received Deduction %",
"domicile": [],
"download": [],
"download-all-holdings": "Download all holdings (xls)",
"etf": [],
"ex-date": [],
"ex-dividend-date": [],
"expenses-collapse": "Expenses & Sales Charge",
"expenses-collapse.cdsc": "CDSC",
"expenses-collapse.gross": "Gross",
"expenses-collapse.max": "Max",
"expenses-collapse.net": "Net",
"expenses-collapse.ratio": "Expense Ratio",
"expenses-collapse.sales-charges": "Sales Charges",
"factsheet": [],
"fav": "FAV",
"favouriteonly": "My Favorites Only",
"federal-id-number": "Federal ID Number",
"find-a-fund": [],
"flag.hard-closure": "Closed to all investments",
"flag.soft-closure": "This share class is closed to new investors",
"for-tax-year": "for Tax Year",
"foreign-tax-credit-information-label": "Foreign Tax Credit Information",
"fund-identifiers": "Fund Identifiers",
"fund-information": [],
"fund-name": "Fund Name",
"fund-number": "Fund Number",
"fund-with-sales-charge": "Fund with Sales Charge (%) (POP)",
"fund-without-sales-charge": "Fund without Sales Charge (%) (NAV)",
"fundname-shareclass-seperator": "-",
"funds-selected": "Funds Selected",
"further-information": "Further-Information",
"geographic-exposure-calculation-type": "% of Total geographic exposure calculation-type default 010 fund is not added",
"gross-exposure-calculation-type": "% of Total gross-exposure for 998",
"gross-exposure-coloum": "Gross Exposure",
"heading": [],
"highest-lowest-nav": [],
"highest-nav": [],
"historical-data-link-text": [
"View Historical Data",
"VIEW HISTORICAL DATA FOR LIQUID ASSETS"
],
"holdings": "Holdings",
"hypothetical-chart-title": "HYPOTHETICAL $10K INVESTMENT",
"important-fund-info": [],
"inception": [
"Inception",
"since",
"Since Inception"
],
"inception-date": "Inception Date",
"income-use": [],
"info-collapse-content": [
"Test Content for Further Information",
"<22>"
],
"info-modal-content": [
"<p>Average annual total return shows the investment's average annual change in value over the indicated periods. It is not the same as a year-by-year annual return. Figures reflect reinvestment of dividends and capital gains.</p>",
"<p>Cumulative total return shows the change in the investment's value over the time period indicated. Figures reflect reinvestment of dividends and capital gains.</p>",
"<p>Net Asset Value (NAV) is the value of one share of the fund on a given day.</p>\n<p>NAV change is the change from the last quoted price.</p>\n<p>The Public Offering Price (POP) is the purchase price for each share of the fund on a given day. It includes the maximum initial sales charge.</p>",
"<p><b>Alpha</b> <br />The difference between a fund's actual returns versus its expected performance, given its level of market risk as measured by beta. A positive alpha indicates the fund performed better than its beta would predict while a negative alpha indicates the fund's under performance based on the expectations indicated by the fund's beta. <br /> <br /><b>Beta</b> <br />A measure of a fund's volatility in relation to the stock market, as measured by a stated index. By definition, the beta of the stated index is 1; a fund with a higher beta has been more volatile than the index, and a fund with a lower beta has been less volatile than the index. <br /> <br /><b>Sharpe Ratio</b> <br />The Sharpe ratio, developed by Nobel Prize winner William Sharpe, provides a measure of a fund's historical risk-adjusted performance. A higher number indicates better historical risk-adjusted performance. <br /> <br /><b>Correlation</b><br />The linear relationship between two return series. Correlation shows the strength of the relationship between two return series. The higher the relationship, the more similar the returns. <br /> <br /><b>R-Squared</b> <br />This number indicates what percentage of a fund's performance fluctuation can be explained by movements in the benchmark index. R-squared (correlation) ranges from 0 to 100. An R-squared of 100 would mean that the fund is tracking its benchmark exactly. A low R-squared indicates that very few of the fund's movements are explained by movements of its benchmark. R-squared can help determine the significance of the fund's beta. A higher R-squared generally indicates a more useful beta. Lower R-squared means the fund's beta is less relevant to its performance. <br /> <br /><b>Standard Deviation</b> <br />This figure provides a statistical measure of the range of a fund's returns. A high standard deviation indicates a wide range of returns and thus greater volatility.</p>",
"<p><b>Direct U.S. Government Obligations</b></p>\n<p>To determine the amount of income derived from these obligations, multiply the total ordinary dividends you received from the fund during the calendar year, as reported on Form 1099-DIV, box 1a by the percentage shown. The percentage of direct U.S. government obligation interest available for tax-exempt treatment may be limited in states that require the fund to meet certain minimum thresholds. Please contact your tax advisor to determine the availability of exemptions in your state.</p>\n<p><b>Specified Private Activity Bond Interest</b></p>\n<p>This percentage represents the amount of Tax-Exempt Interest Dividends paid by a fund that are subject to the alternative minimum tax (AMT). The dollar amount of AMT reportable specified private activity bond interest paid to an individual shareholder is reported on Form 1099-DIV, box 11. Other shareholders can determine the AMT reportable specified private activity bond interest by multiplying the percentage shown by the total Tax-Exempt Income Dividends received during the year as reported on their annual Year-End Asset Summary Statement.</p>\n<p><b>Dividends-Received Deduction</b></p>\n<p>This percentage represents the amount of ordinary dividends paid (including short-term capital gains distributions) during the fund's fiscal year, as income qualifying for the dividends-received deduction.</p>"
],
"info-modal-content-2": "<p><b>Foreign Tax Credit Information</b><br />These amounts represent the foreign taxes paid and foreign source income as designated by the fund that is allocated to shareholders of record on the date(s) noted below. Foreign tax paid allocated to shareholders is reported on Form 1099-DIV, box 6 and is also included in box 1a, Total Ordinary Dividends. Form 1099-DIV also reports foreign source income paid to shareholders.</p>",
"info-modal-content-3": "<p><b>Tax-Exempt Income by Jurisdiction</b><br />This table lists the percentage of your tax-exempt income dividends that may qualify for exemption in your state. (Please contact your tax advisor to determine the taxability of this income in your state.) To determine the amount of income derived from each jurisdiction, multiply the total tax-exempt interest you received from the fund during the calendar year, as reported on Form 1099-DIV, box 10 by the percentage shown.</p>",
"investment": "Investment",
"investment-grade": "Investment Grade",
"isin": "ISIN",
"issuers": "Number of Issuers",
"less-than-50k": "Less than $50,000",
"life": [
"Since Inception",
"Since inception"
],
"long-term-cap-gains-label": "Long-Term <br/>Cap Gains ($)",
"lowest-nav": [],
"manager-allocation-calc-type": [],
"market-value": "Market Value",
"mdash": "&#8212;",
"millions-indicator": "(M)",
"money-funds": "Money Funds",
"month": [],
"month-end": [
"Month End",
"MONTH END",
"Month end"
],
"morningstar-category": "Morningstar Category",
"morningstar-rating": "Morningstar Rating",
"morningstar.not-rated": "This fund is not yet rated.",
"morningstar.rating-too-low": "We do not publish a Morningstar Rating for this fund.",
"name": [
"",
"Name"
],
"nav": "NAV",
"NAV": "NAV",
"NAV-as-of-post": " ",
"NAV-as-of-pre": "on",
"nav-change": "Nav change",
"NAV-change": "NAV Change",
"NAV-high": "NAV High",
"NAV-low": "NAV Low",
"NAV-sub": "(Net Asset Value)",
"nav-total-return": "Average Annual Total Return at",
"nb-funds-in-category": "Number of Funds in Category",
"net-exposure-calculation-type": [
"% of Total for default",
"net-exposure-calculation-type for 948,894, funds"
],
"net-exposure-coloum": "Net Exposure",
"next": "Next",
"no-data-msg": [],
"no-distribution-paid-message": "No Distributions Paid.",
"no-distributions": [],
"no-managers-message": [],
"no-results-message": "No results match your search criteria",
"non-tax-free-income-fund-para": "These amounts represent current year cash distributions paid by the fund and foreign taxes passed through (if any). These amounts are reported to shareholders on Form 1099-DIV.",
"non-taxable-return-of-capital-label": "Non-Taxable <br/>Return of Capital ($)",
"not-applicable-message": "Not applicable to this fund.",
"not-rated": "Not Rated",
"notional-value": "Notional Value",
"of": "of",
"off": "Off",
"on": [
"On",
"on"
],
"ongoing-charges": [],
"ordinary-dividends-info-text": "<p>Ordinary Dividends represent dividends paid by a fund that are derived from interest, dividends, net short-term capital gains and other types of ordinary income earned by the fund. For a fund that elects to pass through its foreign taxes paid (a non-cash item), a shareholders allotted share of foreign taxes has been added to the Ordinary Dividend cash distributions received by the shareholder. Ordinary Dividends equals the amount reported to shareholders on Form 1099-DIV, box 1a.</p>",
"ordinary-dividends-label": "Ordinary <br/>Dividends ($)",
"other": "Other",
"overall": "Overall",
"pay-date-label": "Pay <br/>Date",
"payable-date": [],
"performance-annualised-heading": [],
"performance-tab": "Performance",
"POP": "POP",
"POP-sales-charge-heading": "Pop and Sales Charge at Breakpoints",
"POP-sub": "(Public Offering Price)",
"positions-heading": "Positions",
"positions-not-available": "Not Available.",
"previous": "Previous",
"qualified-dividend-income-info-text": "<p>This amount reflects the portion of a fund's distribution of Ordinary Dividends reported on Form 1099-DIV, box 1a that may be eligible for the 15% tax rate for individuals (0% for lower tax bracket taxpayers). This amount represents both cash distributions and non-cash amounts, such as foreign taxes paid, that have been allocated to shareholders. Qualified Dividend Income equals the amount reported to shareholders on Form 1099-DIV, box 1b.</p>",
"qualified-dividend-income-label": "Qualified <br />Dividend Income ($)",
"quarter-end": [
"Quarter End",
"QUARTER END",
"Quarter end"
],
"ratings-tab": "Ratings",
"read-important": [
"Read important information about results and other investment disclosures",
"<b>\n Read important information about performance results and other disclosures\n</b>"
],
"record-date": [],
"record-date-label": "Record <br/>Date",
"refunded": "Refunded",
"reinvestment-date": [],
"reinvestment-price": [],
"reset-to-current": "Reset to Current",
"sales-charge": "Sales Charge",
"salescharges": "Sales Charges",
"schedules": [],
"search-fields": [
"Filter by keyword",
"Filter by Keyword"
],
"sector": "Sector",
"security-name": "Security Name",
"share-class": "Class",
"share-prices-heading": "Share Prices ($)",
"show-performance": "Show Performance",
"showing": "Showing",
"specified-private-activity-bond-interest": "Specified Private Activity Bond Interest %",
"statistics-heading": "Statistics",
"subheading": [],
"symbol": "TICKER",
"table": "Table",
"table-heading": "% of Total (Exposure)",
"target-allocation": "Target Allocation",
"target-allocation-type": [],
"tax-exempt-income-jurisdiction-label": "Tax-Exempt Income Jurisdiction",
"tax-exempt-income-label": "Tax-Exempt <br/>Income ($)",
"tax-year": "Tax year:",
"ter": [],
"ticker": "Ticker",
"total-distributions-label": "Total <br/>Distributions ($)",
"total-label": "Total",
"updated-daily": "(updated daily)",
"updated-monthly": "(updated monthly)",
"value": [
"",
"Value"
],
"view-historical-data": [],
"weight": "Weight (%)",
"year": [],
"year-end-message": "Tax information will be posted by mid-February.",
"year-to-date-distributions-per-share": [],
"yield-tab": "Yield",
"yield.w-waiver": "w/ waiver",
"yield.wo-waiver": "w/o waiver",
"ytd": [
"YTD",
"ytd"
],
"ytd-total-ret": [
"YTD Total return at Nav",
"<abbr title=\"Year to Date\">YTD</abbr> Total return at Nav"
]
}

BIN
microfab.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,2 @@
auxiliary.org-netbeans-modules-javascript-karma.config=karma.conf.js
browser=Chrome.INTEGRATED

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<_0:editor-bookmarks xmlns:_0="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
<group/>
</open-files>
</project-private>

View File

@ -0,0 +1,22 @@
auxiliary.org-netbeans-modules-css-prep.less_2e_compiler_2e_options=
auxiliary.org-netbeans-modules-css-prep.less_2e_enabled=false
auxiliary.org-netbeans-modules-css-prep.less_2e_mappings=/less:/css
auxiliary.org-netbeans-modules-css-prep.sass_2e_compiler_2e_options=
auxiliary.org-netbeans-modules-css-prep.sass_2e_configured=true
auxiliary.org-netbeans-modules-css-prep.sass_2e_enabled=false
auxiliary.org-netbeans-modules-css-prep.sass_2e_mappings=/scss:/css
auxiliary.org-netbeans-modules-javascript-nodejs.enabled=false
auxiliary.org-netbeans-modules-javascript-nodejs.node_2e_default=true
auxiliary.org-netbeans-modules-javascript-nodejs.run_2e_enabled=false
auxiliary.org-netbeans-modules-javascript-nodejs.sync_2e_enabled=true
auxiliary.org-netbeans-modules-javascript2-json.json_2e_comments=true
auxiliary.org-netbeans-modules-javascript2-requirejs.enabled=true
auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=js/libs
browser.run=true
file.reference.frontend-projects-web-platform=.
file.reference.web-platform-src=src
files.encoding=UTF-8
site.root.folder=${file.reference.frontend-projects-web-platform}
source.folder=${file.reference.web-platform-src}
start.file=index.html
web.context.root=/

9
nbproject/project.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.web.clientproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/clientside-project/1">
<name>ft-web-platform</name>
</data>
</configuration>
</project>

BIN
new2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

127
package.json Normal file
View File

@ -0,0 +1,127 @@
{
"name": "ft-web-platform",
"description": "Platform functionality for retail web sites",
"version": "0.1.0",
"license": "Franklin Templeton Investments",
"repository": {
"type": "svn",
"url": "https://sourcemgmtsvn.noam.corp.frk.com:18080/svn/globalweb/branches/GW-Maintenance/frontend-projects/web-platform"
},
"private": true,
"scripts": {
"test": "jest",
"test-reducers": "jest --config jest-reducers.json --coverage",
"test-portfolio-reducers": "jest --config jest-portfolio-reducers.json --coverage --watch",
"test-ppss-reducers": "jest --config jest-ppss-reducers.json --coverage --watch",
"coverage": "jest --coverage",
"au-build": "au build",
"au-build-dev": "au build --dev",
"au-build-js": "au build --js",
"au-build-css": "au build --alan",
"au-lint-css": "au lint-css",
"au-lint-js": "au lint-js",
"au-process-assets": "au process-assets",
"au-process-css": "au process-css",
"au-process-markup": "au process-markup",
"au-test": "au test --bl",
"au-run": "au run --watch",
"au-run-dev": "au run --watch --dev"
},
"jest": {
"verbose": true,
"rootDir": "src"
},
"dependencies": {
"aurelia-animator-css": "^1.0.1",
"aurelia-bootstrapper": "^2.1.1",
"aurelia-fetch-client": "^1.1.2",
"aurelia-framework": "^1.1.0",
"aurelia-pal-browser": "^1.2.0",
"bluebird": "^3.5.0",
"highcharts": "^5.0.9",
"lodash": "^4.17.4",
"moment": "^2.18.1",
"moment-parseformat": "^2.1.4",
"redux": "^3.6.0",
"requirejs": "^2.3.3",
"requirejs-text": "^2.0.15",
"responsive-toolkit": "^2.6.3",
"whatwg-fetch": "^2.0.3"
},
"peerDependencies": {},
"devDependencies": {
"attrchange": "^1.0.1",
"aurelia-cli": "^0.29.0",
"aurelia-testing": "^1.0.0-beta.2.0.1",
"aurelia-tools": "^1.0.0",
"autoprefixer": "^6.7.7",
"babel-eslint": "^6.0.4",
"babel-plugin-syntax-flow": "^6.8.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-es2015-modules-amd": "^6.8.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.10.3",
"babel-plugin-transform-flow-strip-types": "^6.8.0",
"babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.9.0",
"babel-preset-stage-1": "^6.5.0",
"babel-register": "^6.9.0",
"bootstrap": "^3.3.7",
"bootstrap-sass": "^3.3.7",
"browser-sync": "^2.13.0",
"compression": "^1.6.2",
"connect-history-api-fallback": "^1.2.0",
"express": "^4.15.3",
"express-cache-response": "^1.0.2",
"express-http-proxy": "^1.0.3",
"glob": "^7.1.1",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-babel": "^6.1.2",
"gulp-changed-in-place": "^2.0.3",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.1",
"gulp-cssnano": "^2.1.2",
"gulp-debug": "^3.0.0",
"gulp-download": "0.0.1",
"gulp-eslint": "^2.1.0",
"gulp-htmlmin": "^3.0.0",
"gulp-jest": "^1.0.0",
"gulp-json-format": "^1.0.0",
"gulp-lintspaces": "^0.5.0",
"gulp-notify": "^2.2.0",
"gulp-plumber": "^1.1.0",
"gulp-postcss": "^6.2.0",
"gulp-rename": "^1.2.2",
"gulp-sass": "^3.0.0",
"gulp-sonar": "^3.0.1",
"gulp-sourcemaps": "^2.0.0-alpha",
"gulp-stylelint": "^3.6.1",
"gulp-uglify": "^2.1.2",
"gulp-util": "^3.0.8",
"gulp-zip": "^3.2.0",
"highlight.js": "^9.9.0",
"http-proxy-middleware": "^0.17.4",
"jasmine-core": "^2.4.1",
"jest": "^18.0.0",
"jest-cli": "^18.1.0",
"jquery": "^3.1.1",
"jquery-ui": "^1.12.1",
"jsonfile": "^3.0.0",
"karma": "^0.13.22",
"karma-babel-preprocessor": "^6.0.1",
"karma-chrome-launcher": "^1.0.1",
"karma-coverage": "^1.1.1",
"karma-jasmine": "^1.0.2",
"karma-spec-reporter": "0.0.26",
"marked": "^0.3.6",
"minimatch": "^3.0.2",
"mockdate": "^2.0.1",
"owl.carousel": "^2.2.0",
"pretty-hrtime": "^1.0.3",
"request": "^2.81.0",
"stylelint-config-standard": "^15.0.0",
"through2": "^2.0.1",
"uglify-js": "^2.6.3",
"vinyl": "^2.0.1",
"vinyl-map2": "^1.2.1"
}
}

231
scripts/getLabels.js Normal file
View File

@ -0,0 +1,231 @@
/**
* Created by mdonnel on 11/05/2017.
*/
const meow = require('meow');
const util = require('util');
const jsonfile = require('jsonfile');
const fs = require('fs');
const glob = require('glob');
const chalk = require('chalk');
const path = require('path');
const _ = require('lodash');
const cli = meow({
help: `
Usage
node getLabels.js -p <path> <options>
Path
Where to check for html files. Only looks at .html files.
Outputs
Outputs a labels.json file
Options
-p, --path Path to search for HTMl files
-r, --replace Replace label['id'] with getLabel('id')
-l, --label Path to search for JSON files
Examples
# Check all the html files in a given folder
node getLabels.js -p C:\\dev\\web-platform\\src\\components\\products
# Check all the html files in a given folder then replace all old label
references with the new method
node getLabels.js -p C:\\dev\\web-platform\\src\\components\\products --replace
`},
{
alias: {
p: 'path',
l: 'labels',
r: 'replace'
},
default: {
path: null,
labels: null,
replace: false
},
boolean: [ 'replace'],
string: ['path', 'labels']
});
const globOptions = {};
const options = {
path: cli.flags.path,
replace: cli.flags.replace,
labels: cli.flags.labels
};
const labelsPattern = /label\[\s?\'(\S+)\'\s?\]/g;
const getLabelsPattern = /getLabel\(\s?\'(\S+)\'\s?\)/g;
const fileArray = './labels-array.json';
const fileObject = './labels-object.json';
const log = function() {
process.stdout.write(util.format.apply(this, arguments) + '\n');
};
function consolidateLabels(a) {
const [labelList, labelObj] = a;
const newLabels = {};
return new Promise(function(resolve, reject) {
if (typeof labelObj === 'undefined') {
return resolve();
}
log('Consolidating labels...');
for (const listItem of labelList) {
const newEntry = [];
labelObj.forEach((obj) => {
if (Object.keys(obj).indexOf(listItem) !== -1) {
const tempStr = obj[listItem];
if (newEntry.indexOf(tempStr) === -1) {
newEntry.push(tempStr);
}
}
newLabels[listItem] = (newEntry.length === 1) ? newEntry[0] : newEntry;
});
}
return resolve(newLabels);
});
}
function getLabels(rawLabels) {
const newLabels = {};
const lSource = _.get(rawLabels, 'provider.labels.label');
if (typeof lSource !== 'undefined') {
for (const item of lSource) {
const key = _.get(item, 'key');
newLabels[`${key.toString()}`] = _.get(item, 'value');
}
}
return newLabels;
}
function getAllLabels(labelPath, d) {
const searchPath = `${labelPath}\\**\\*.json`;
const found = [];
return new Promise(function(resolve, reject) {
if (labelPath === null) {
return resolve [d];
}
log(`Looking for label files: ${labelPath}`);
glob(searchPath, globOptions, function(er, files) {
files.forEach(function(element) {
if (path.parse(element).name.indexOf('label') !== -1) {
log(element);
const labelfile = jsonfile.readFileSync(element);
const labeldata = getLabels(labelfile);
found.push(labeldata);
}
});
return resolve([d, found]);
});
});
}
function processFiles(srcPath, doReplace = false) {
const searchPath = `${srcPath}\\**\\*.html`;
log(`Searching: ${searchPath}`);
return new Promise(function(resolve, reject) {
let found = [];
glob(searchPath, globOptions, function(er, files) {
files.forEach(function(element) {
try {
const openedFile = fs.readFileSync(element, 'utf8');
let matched;
// find plain labels
matched = labelsPattern.exec(openedFile);
while (matched) {
const str = matched[1];
if (found.indexOf(str) === -1) {
found.push(str);
}
matched = labelsPattern.exec(openedFile);
}
// find new getLabels
matched = getLabelsPattern.exec(openedFile);
while (matched) {
const str = matched[1];
if (found.indexOf(str) === -1) {
found.push(str);
}
matched = getLabelsPattern.exec(openedFile);
}
if (doReplace && labelsPattern.test(openedFile)) {
log(`Updating: ${chalk.cyan(truncatePath(element, 4))}`);
const newFile = openedFile.replace(labelsPattern, 'getLabel(\'$1\')');
fs.writeFile(element, newFile);
}
} catch (e) {
return reject(e);
}
});
found = found.sort(function(a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
return resolve(found);
});
});
}
function truncatePath(pathStr, parts) {
const oldBits = pathStr.split('/');
return ['...'].concat(oldBits.slice(oldBits.length - (parts - 1))).join('/');
}
function saveJSON(data) {
log(chalk.yellow('Saving...'));
jsonfile.writeFileSync(fileArray, data);
return data;
}
function saveJSONObject(data) {
log(chalk.yellow('Saving...'));
jsonfile.writeFileSync(fileObject, data);
return data;
}
function processLabels(opt) {
log('GetLabels');
if (opt.path !== null) {
log(chalk.magenta('Working...'));
processFiles(opt.path, opt.replace)
.then((f) => {
return saveJSON(f);
})
.then(function(d) {
return getAllLabels(opt.labels, d);
})
.then((d) => {
return consolidateLabels(d);
})
.then((f) => {
return saveJSONObject(f);
})
.then(() => {
log(chalk.green('Done.'));
}).catch((e) => {
log(chalk.red(e));
});
} else {
log('Nothing to do. Try adding a path. Or use --help for help.');
}
}
processLabels(options);

File diff suppressed because one or more lines are too long

340
scripts/labels-object.json Normal file
View File

@ -0,0 +1,340 @@
{
"1-yr": [
"1 Yr",
"1 YR"
],
"10-Hypo-text-Bottom": [],
"10-Hypo-text-Top": [],
"10-yr": [
"10 Yr",
"10 YR"
],
"10yr": "10 YR",
"15-yr": [],
"1yr": "1 YR",
"250k-499k": [],
"3-yr": [
"3 Yr",
"3 YR"
],
"3yr": "3 YR",
"5-yr": [
"5 Yr",
"5 YR"
],
"500k-999k": [],
"50k-99k": [],
"52-week-range-heading": "52-week Range ($)",
"5yr": "5 YR",
"7-day-current-yield": "7 Day Current Yield",
"7-day-effective-yield": "7 Day Effective Yield",
"actual-allocation": "Actual Allocation",
"actual-allocation-type": [],
"add-to-factsheet-to-cart": "Add Fact Sheet to My Cart",
"add-to-favourite.add": "Add to my Favorites",
"additional-tax-information": "Additional Tax Information",
"advantage-hypo-para": "Create a client-use report of the fund's performance, tailored for a specific client.",
"after-shares-sold": "After Shares Sold",
"all": "All",
"all-fund-distribution-message": "All fund distributions will vary depending on current market conditions, and past distributions are not indicative of future trends.",
"allocation": [],
"amount": [],
"AS_OF_DATE": [],
"as-of": [
"As of",
"As of "
],
"asset-class-exposure-calculation-type": "% of Total asset exposure calculation-type",
"assetclass": "Asset Class",
"at-nav": "at NAV",
"ataatr-info-text": [],
"aum": [],
"before-shares-sold": "Before Shares Sold",
"below-investment-grade": "Below Investment Grade",
"calendar-year-1yr": "Year 1",
"calendar-year-2yr": "Year 2",
"calendar-year-3yr": "Year 3",
"calendar-year-4yr": "Year 4",
"calendar-year-5yr": "Year 5",
"calendar-year-6yr": "Year 6",
"capital-gain-distributions": [],
"cash-and-cash-equv": "Cash &amp; Cash Equivalents",
"category": [],
"chart": "Chart",
"chart-alternate": "No chart available for this data.",
"close": [
"Close",
"close"
],
"compare-selected-funds": "Compare Selected Funds",
"component-heading": [
"Average Annual Total Returns",
"Cumulative Total Returns",
"HISTORICAL DATA - After-Tax Average Annual Total Returns",
"HISTORICAL DATA - Average Annual Total Returns",
"HISTORICAL DATA - Cumulative Total Returns",
"Geographic Allocation",
"Market Capitalisation",
"Sector Allocation",
"Geographic Breakdown",
"Portfolio",
"Asset Class Exposure",
"Asset Allocation",
"Commodities Sector Breakdown",
"Coupon Rate",
"credit quality exposure",
"CURRENCY DISTRIBUTION",
"Daily Fund Prices and Breakpoints",
"Fixed Income Sector Breakdown",
"Geographic Exposure",
"Manager Roster",
"Maturity Breakdown",
"Pricing at",
"Quality Breakdown",
"sector breakdown",
"sector exposure",
"Strategy Breakdown",
"Tax Information",
"Top Five Holdings",
"TOP TEN HOLDINGS"
],
"component-sub-heading": [
"AFTER-TAX AVERAGE ANNUAL TOTAL RETURNS",
"Average Annual Total Returns",
"Cumulative Total Returns"
],
"component-subheading": [],
"component-subheading-sector-exposure": "sub heading",
"contact-us-info": "For information on tax years prior to 2008, please contact us.",
"cumulative-performance-heading": [],
"currency": "Currency",
"daily-fund-prices": [],
"date-selector-info": "Choose a month and year from the drop-menus above to show historical performance data.",
"direct-usgovt-obligations": "Direct U.S.Gov't Obligations %",
"discrete-1yr": "Dec-15 / Dec-16",
"discrete-2yr": "Dec-14 / Dec-15",
"discrete-3yr": "Dec-13 / Dec-14",
"discrete-4yr": "Dec-12 / Dec-13",
"discrete-5yr": "Dec-11 / Dec-12",
"distribution-date": [],
"distribution-rate-and-breakpoints": [],
"distributions": [
"Distributions",
"DISTRIBUTIONS (PER SHARE)"
],
"distributions-estimates": [],
"dividend-distributions": [],
"dividends": [],
"dividends-per-share": [],
"dividends-received-deduction": "Dividends-Received Deduction %",
"domicile": [],
"download": [],
"download-all-holdings": "Download all holdings (xls)",
"etf": [],
"ex-date": [],
"ex-dividend-date": [],
"expenses-collapse": "Expenses & Sales Charge",
"expenses-collapse.cdsc": "CDSC",
"expenses-collapse.gross": "Gross",
"expenses-collapse.max": "Max",
"expenses-collapse.net": "Net",
"expenses-collapse.ratio": "Expense Ratio",
"expenses-collapse.sales-charges": "Sales Charges",
"factsheet": [],
"fav": "FAV",
"favouriteonly": "My Favorites Only",
"federal-id-number": "Federal ID Number",
"find-a-fund": [],
"flag.hard-closure": "Closed to all investments",
"flag.soft-closure": "This share class is closed to new investors",
"for-tax-year": "for Tax Year",
"foreign-tax-credit-information-label": "Foreign Tax Credit Information",
"fund-identifiers": "Fund Identifiers",
"fund-information": [],
"fund-name": "Fund Name",
"fund-number": "Fund Number",
"fund-with-sales-charge": "Fund with Sales Charge (%) (POP)",
"fund-without-sales-charge": "Fund without Sales Charge (%) (NAV)",
"fundname-shareclass-seperator": "-",
"funds-selected": "Funds Selected",
"further-information": "Further-Information",
"geographic-exposure-calculation-type": "% of Total geographic exposure calculation-type default 010 fund is not added",
"gross-exposure-calculation-type": "% of Total gross-exposure for 998",
"gross-exposure-coloum": "Gross Exposure",
"heading": [],
"highest-lowest-nav": [],
"highest-nav": [],
"historical-data-link-text": [
"View Historical Data",
"VIEW HISTORICAL DATA FOR LIQUID ASSETS"
],
"holdings": "Holdings",
"hypothetical-chart-title": "HYPOTHETICAL $10K INVESTMENT",
"important-fund-info": [],
"inception": [
"Inception",
"since",
"Since Inception"
],
"inception-date": "Inception Date",
"income-use": [],
"info-collapse-content": [
"Test Content for Further Information",
"<22>"
],
"info-modal-content": [
"<p>Average annual total return shows the investment's average annual change in value over the indicated periods. It is not the same as a year-by-year annual return. Figures reflect reinvestment of dividends and capital gains.</p>",
"<p>Cumulative total return shows the change in the investment's value over the time period indicated. Figures reflect reinvestment of dividends and capital gains.</p>",
"<p>Net Asset Value (NAV) is the value of one share of the fund on a given day.</p>\n<p>NAV change is the change from the last quoted price.</p>\n<p>The Public Offering Price (POP) is the purchase price for each share of the fund on a given day. It includes the maximum initial sales charge.</p>",
"<p><b>Alpha</b> <br />The difference between a fund's actual returns versus its expected performance, given its level of market risk as measured by beta. A positive alpha indicates the fund performed better than its beta would predict while a negative alpha indicates the fund's under performance based on the expectations indicated by the fund's beta. <br /> <br /><b>Beta</b> <br />A measure of a fund's volatility in relation to the stock market, as measured by a stated index. By definition, the beta of the stated index is 1; a fund with a higher beta has been more volatile than the index, and a fund with a lower beta has been less volatile than the index. <br /> <br /><b>Sharpe Ratio</b> <br />The Sharpe ratio, developed by Nobel Prize winner William Sharpe, provides a measure of a fund's historical risk-adjusted performance. A higher number indicates better historical risk-adjusted performance. <br /> <br /><b>Correlation</b><br />The linear relationship between two return series. Correlation shows the strength of the relationship between two return series. The higher the relationship, the more similar the returns. <br /> <br /><b>R-Squared</b> <br />This number indicates what percentage of a fund's performance fluctuation can be explained by movements in the benchmark index. R-squared (correlation) ranges from 0 to 100. An R-squared of 100 would mean that the fund is tracking its benchmark exactly. A low R-squared indicates that very few of the fund's movements are explained by movements of its benchmark. R-squared can help determine the significance of the fund's beta. A higher R-squared generally indicates a more useful beta. Lower R-squared means the fund's beta is less relevant to its performance. <br /> <br /><b>Standard Deviation</b> <br />This figure provides a statistical measure of the range of a fund's returns. A high standard deviation indicates a wide range of returns and thus greater volatility.</p>",
"<p><b>Direct U.S. Government Obligations</b></p>\n<p>To determine the amount of income derived from these obligations, multiply the total ordinary dividends you received from the fund during the calendar year, as reported on Form 1099-DIV, box 1a by the percentage shown. The percentage of direct U.S. government obligation interest available for tax-exempt treatment may be limited in states that require the fund to meet certain minimum thresholds. Please contact your tax advisor to determine the availability of exemptions in your state.</p>\n<p><b>Specified Private Activity Bond Interest</b></p>\n<p>This percentage represents the amount of Tax-Exempt Interest Dividends paid by a fund that are subject to the alternative minimum tax (AMT). The dollar amount of AMT reportable specified private activity bond interest paid to an individual shareholder is reported on Form 1099-DIV, box 11. Other shareholders can determine the AMT reportable specified private activity bond interest by multiplying the percentage shown by the total Tax-Exempt Income Dividends received during the year as reported on their annual Year-End Asset Summary Statement.</p>\n<p><b>Dividends-Received Deduction</b></p>\n<p>This percentage represents the amount of ordinary dividends paid (including short-term capital gains distributions) during the fund's fiscal year, as income qualifying for the dividends-received deduction.</p>"
],
"info-modal-content-2": "<p><b>Foreign Tax Credit Information</b><br />These amounts represent the foreign taxes paid and foreign source income as designated by the fund that is allocated to shareholders of record on the date(s) noted below. Foreign tax paid allocated to shareholders is reported on Form 1099-DIV, box 6 and is also included in box 1a, Total Ordinary Dividends. Form 1099-DIV also reports foreign source income paid to shareholders.</p>",
"info-modal-content-3": "<p><b>Tax-Exempt Income by Jurisdiction</b><br />This table lists the percentage of your tax-exempt income dividends that may qualify for exemption in your state. (Please contact your tax advisor to determine the taxability of this income in your state.) To determine the amount of income derived from each jurisdiction, multiply the total tax-exempt interest you received from the fund during the calendar year, as reported on Form 1099-DIV, box 10 by the percentage shown.</p>",
"investment": "Investment",
"investment-grade": "Investment Grade",
"isin": "ISIN",
"issuers": "Number of Issuers",
"less-than-50k": "Less than $50,000",
"life": [
"Since Inception",
"Since inception"
],
"long-term-cap-gains-label": "Long-Term <br/>Cap Gains ($)",
"lowest-nav": [],
"manager-allocation-calc-type": [],
"market-value": "Market Value",
"mdash": "&#8212;",
"millions-indicator": "(M)",
"money-funds": "Money Funds",
"month": [],
"month-end": [
"Month End",
"MONTH END",
"Month end"
],
"morningstar-category": "Morningstar Category",
"morningstar-rating": "Morningstar Rating",
"morningstar.not-rated": "This fund is not yet rated.",
"morningstar.rating-too-low": "We do not publish a Morningstar Rating for this fund.",
"name": [
"",
"Name"
],
"nav": "NAV",
"NAV": "NAV",
"NAV-as-of-post": " ",
"NAV-as-of-pre": "on",
"nav-change": "Nav change",
"NAV-change": "NAV Change",
"NAV-high": "NAV High",
"NAV-low": "NAV Low",
"NAV-sub": "(Net Asset Value)",
"nav-total-return": "Average Annual Total Return at",
"nb-funds-in-category": "Number of Funds in Category",
"net-exposure-calculation-type": [
"% of Total for default",
"net-exposure-calculation-type for 948,894, funds"
],
"net-exposure-coloum": "Net Exposure",
"next": "Next",
"no-data-msg": [],
"no-distribution-paid-message": "No Distributions Paid.",
"no-distributions": [],
"no-managers-message": [],
"no-results-message": "No results match your search criteria",
"non-tax-free-income-fund-para": "These amounts represent current year cash distributions paid by the fund and foreign taxes passed through (if any). These amounts are reported to shareholders on Form 1099-DIV.",
"non-taxable-return-of-capital-label": "Non-Taxable <br/>Return of Capital ($)",
"not-applicable-message": "Not applicable to this fund.",
"not-rated": "Not Rated",
"notional-value": "Notional Value",
"of": "of",
"off": "Off",
"on": [
"On",
"on"
],
"ongoing-charges": [],
"ordinary-dividends-info-text": "<p>Ordinary Dividends represent dividends paid by a fund that are derived from interest, dividends, net short-term capital gains and other types of ordinary income earned by the fund. For a fund that elects to pass through its foreign taxes paid (a non-cash item), a shareholders allotted share of foreign taxes has been added to the Ordinary Dividend cash distributions received by the shareholder. Ordinary Dividends equals the amount reported to shareholders on Form 1099-DIV, box 1a.</p>",
"ordinary-dividends-label": "Ordinary <br/>Dividends ($)",
"other": "Other",
"overall": "Overall",
"pay-date-label": "Pay <br/>Date",
"payable-date": [],
"performance-annualised-heading": [],
"performance-tab": "Performance",
"POP": "POP",
"POP-sales-charge-heading": "Pop and Sales Charge at Breakpoints",
"POP-sub": "(Public Offering Price)",
"positions-heading": "Positions",
"positions-not-available": "Not Available.",
"previous": "Previous",
"qualified-dividend-income-info-text": "<p>This amount reflects the portion of a fund's distribution of Ordinary Dividends reported on Form 1099-DIV, box 1a that may be eligible for the 15% tax rate for individuals (0% for lower tax bracket taxpayers). This amount represents both cash distributions and non-cash amounts, such as foreign taxes paid, that have been allocated to shareholders. Qualified Dividend Income equals the amount reported to shareholders on Form 1099-DIV, box 1b.</p>",
"qualified-dividend-income-label": "Qualified <br />Dividend Income ($)",
"quarter-end": [
"Quarter End",
"QUARTER END",
"Quarter end"
],
"ratings-tab": "Ratings",
"read-important": [
"Read important information about results and other investment disclosures",
"<b>\n Read important information about performance results and other disclosures\n</b>"
],
"record-date": [],
"record-date-label": "Record <br/>Date",
"refunded": "Refunded",
"reinvestment-date": [],
"reinvestment-price": [],
"reset-to-current": "Reset to Current",
"sales-charge": "Sales Charge",
"salescharges": "Sales Charges",
"schedules": [],
"search-fields": [
"Filter by keyword",
"Filter by Keyword"
],
"sector": "Sector",
"security-name": "Security Name",
"share-class": "Class",
"share-prices-heading": "Share Prices ($)",
"show-performance": "Show Performance",
"showing": "Showing",
"specified-private-activity-bond-interest": "Specified Private Activity Bond Interest %",
"statistics-heading": "Statistics",
"subheading": [],
"symbol": "TICKER",
"table": "Table",
"table-heading": "% of Total (Exposure)",
"target-allocation": "Target Allocation",
"target-allocation-type": [],
"tax-exempt-income-jurisdiction-label": "Tax-Exempt Income Jurisdiction",
"tax-exempt-income-label": "Tax-Exempt <br/>Income ($)",
"tax-year": "Tax year:",
"ter": [],
"ticker": "Ticker",
"total-distributions-label": "Total <br/>Distributions ($)",
"total-label": "Total",
"updated-daily": "(updated daily)",
"updated-monthly": "(updated monthly)",
"value": [
"",
"Value"
],
"view-historical-data": [],
"weight": "Weight (%)",
"year": [],
"year-end-message": "Tax information will be posted by mid-February.",
"year-to-date-distributions-per-share": [],
"yield-tab": "Yield",
"yield.w-waiver": "w/ waiver",
"yield.wo-waiver": "w/o waiver",
"ytd": [
"YTD",
"ytd"
],
"ytd-total-ret": [
"YTD Total return at Nav",
"<abbr title=\"Year to Date\">YTD</abbr> Total return at Nav"
]
}

1
scripts/labels.json Normal file
View File

@ -0,0 +1 @@
["1-yr","10-Hypo-text-Bottom","10-Hypo-text-Top","10-yr","10yr","15-yr","1yr","250k-499k","3-yr","3yr","5-yr","500k-999k","50k-99k","52-week-range-heading","5yr","7-day-current-yield","7-day-effective-yield","actual-allocation","actual-allocation-type","add-to-factsheet-to-cart","add-to-favourite.add","additional-tax-information","advantage-hypo-para","after-shares-sold","all","all-fund-distribution-message","allocation","amount","AS_OF_DATE","as-of","asset-class-exposure-calculation-type","assetclass","at-nav","ataatr-info-text","aum","before-shares-sold","below-investment-grade","calendar-year-1yr","calendar-year-2yr","calendar-year-3yr","calendar-year-4yr","calendar-year-5yr","calendar-year-6yr","capital-gain-distributions","cash-and-cash-equv","category","chart","chart-alternate","close","compare-selected-funds","component-heading","component-sub-heading","component-subheading","component-subheading-sector-exposure","contact-us-info","cumulative-performance-heading","currency","daily-fund-prices","date-selector-info","direct-usgovt-obligations","discrete-1yr","discrete-2yr","discrete-3yr","discrete-4yr","discrete-5yr","distribution-date","distribution-rate-and-breakpoints","distributions","distributions-estimates","dividend-distributions","dividends","dividends-per-share","dividends-received-deduction","domicile","download","download-all-holdings","etf","ex-date","ex-dividend-date","expenses-collapse","expenses-collapse.cdsc","expenses-collapse.gross","expenses-collapse.max","expenses-collapse.net","expenses-collapse.ratio","expenses-collapse.sales-charges","factsheet","fav","favouriteonly","federal-id-number","find-a-fund","flag.hard-closure","flag.soft-closure","for-tax-year","foreign-tax-credit-information-label","fund-identifiers","fund-information","fund-name","fund-number","fund-with-sales-charge","fund-without-sales-charge","fundname-shareclass-seperator","funds-selected","further-information","geographic-exposure-calculation-type","gross-exposure-calculation-type","gross-exposure-coloum","heading","highest-lowest-nav","highest-nav","historical-data-link-text","holdings","hypothetical-chart-title","important-fund-info","inception","inception-date","income-use","info-collapse-content","info-modal-content","info-modal-content-2","info-modal-content-3","investment","investment-grade","isin","issuers","less-than-50k","life","long-term-cap-gains-label","lowest-nav","manager-allocation-calc-type","market-value","mdash","millions-indicator","money-funds","month","month-end","morningstar-category","morningstar-rating","morningstar.not-rated","morningstar.rating-too-low","name","nav","NAV","NAV-as-of-post","NAV-as-of-pre","nav-change","NAV-change","NAV-high","NAV-low","NAV-sub","nav-total-return","nb-funds-in-category","net-exposure-calculation-type","net-exposure-coloum","next","no-data-msg","no-distribution-paid-message","no-distributions","no-managers-message","no-results-message","non-tax-free-income-fund-para","non-taxable-return-of-capital-label","not-applicable-message","not-rated","notional-value","of","off","on","ongoing-charges","ordinary-dividends-info-text","ordinary-dividends-label","other","overall","pay-date-label","payable-date","performance-annualised-heading","performance-tab","POP","POP-sales-charge-heading","POP-sub","positions-heading","positions-not-available","previous","qualified-dividend-income-info-text","qualified-dividend-income-label","quarter-end","ratings-tab","read-important","record-date","record-date-label","refunded","reinvestment-date","reinvestment-price","reset-to-current","sales-charge","salescharges","schedules","search-fields","sector","security-name","share-class","share-prices-heading","show-performance","showing","specified-private-activity-bond-interest","statistics-heading","subheading","symbol","table","table-heading","target-allocation","target-allocation-type","tax-exempt-income-jurisdiction-label","tax-exempt-income-label","tax-year","ter","ticker","total-distributions-label","total-label","updated-daily","updated-monthly","value","view-historical-data","weight","year","year-end-message","year-to-date-distributions-per-share","yield-tab","yield.w-waiver","yield.wo-waiver","ytd","ytd-total-ret"]

View File

@ -0,0 +1,53 @@
import {inject/*, LogManager*/} from 'aurelia-framework';
// const logger = LogManager.getLogger('performance-annualised');
@inject('Store', Element)
export class shareClassLinkListenerCustomAttribute {
constructor(store, element) {
this.element = element;
store.subscribe(() => this.mapState(store.getState()));
}
queryStringToJSON(href) {
let params = href.slice(1).split('&');
let result = {};
for (let param of params) {
param = param.split('=');
result[param[0]] = decodeURIComponent(param[1] || '');
}
return JSON.parse(JSON.stringify(result));
}
mapState(newState) {
try {
let currentPath = this.element.pathname;
let currentQueryObject = this.queryStringToJSON(this.element.search);
let storeFundId = newState.products.app.currentFundId;
let storeShareClass = newState.products.app.currentShareClass;
let newFundId;
let newShareClass;
if (storeFundId !== 0) {
newFundId = (storeFundId === currentQueryObject['FundID']) ? currentQueryObject['FundID'] : storeFundId;
} else {
newFundId = currentQueryObject['FundID'];
}
if (storeShareClass !== '') {
newShareClass = (storeShareClass === currentQueryObject['ShareClass']) ? currentQueryObject['ShareClass'] : storeShareClass;
} else {
newShareClass = currentQueryObject['ShareClass'];
}
let newHref = currentPath + '?' + 'FundID=' + newFundId + '&ShareClass=' + newShareClass;
this.element.href = newHref;
} catch (e) {
//logger.error(e);
}
}
}

View File

@ -0,0 +1,8 @@
**Share class link listener**
This is a custom attribute which listens for changes made to the href attributes of anchor tags which have the
*share-class-link-listener* attribute. These changes to the href are made from selecting a different class in the *ft-share-class-filter*
custom element, present on individual Fund pages.
Upon a state change, it compares the current href query string parameters against those in the store, and if the current
ones differ from the store, it runs a routine to update the params accordingly.

View File

@ -0,0 +1,5 @@
.ft-chart {
display: block;
min-height: 500px;
}

View File

@ -0,0 +1,101 @@
import {bindable, useView, computedFrom} from 'aurelia-framework';
import {FtChart} from './ft-chart';
@useView('./ft-chart.html')
export class FtBarChart extends FtChart {
// @bindable options;
@bindable data = [];
@bindable suffix = '';
@bindable prefix = '';
@bindable type = '';
@computedFrom('data', 'suffix', 'prefix', 'type')
get series() {
return this.data;
}
get options() {
let suffix = this.suffix;
let prefix = this.prefix;
let type = this.type;
let _self = this;
return {
chart: {
colorByPoint: true,
type: type
},
colors: [
'#005598', '#4e9d2d', '#00a0dc', '#333333', '#ed7700',
'#8a1811', '#163758', '#81bb00', '#5fc0eb', '#767676',
'#f7bb00', '#cb0000', '#bad583', '#a5d7f5', '#a7a7a7',
'#f5db8b', '#d3e3b0', '#DBEFFB', '#c9c9c9', '#e8e8e8'
],
tooltip: {
formatter: function() {
if (_self.data.length > 1) {
return `${this.point.name}<br /><span style="font-size:14px;color:${this.point.series.color}">${this.point.series.name}</span>${prefix}${Highcharts.numberFormat(this.point.y, 2)}${suffix}`;
}
return `${this.point.name}<br />${prefix}${Highcharts.numberFormat(this.point.y, 2)}${suffix}`;
}
},
xAxis: {
type: 'category',
labels: {
enabled: (this.data.length > 1)
}
},
yAxis: {
title: {
enabled: false
},
labels: {
useHTML: true,
formatter: function() {
return `${prefix}${Highcharts.numberFormat(this.value, 1)}${suffix}`;
}
}
},
legend: {
enabled: (this.data.length > 1),
align: 'left',
verticalAlign: 'top',
layout: 'horizontal',
x: 0,
symbolRadius: 0,
itemHoverStyle: {
cursor: 'default'
}
},
plotOptions: {
series: {
events: {
legendItemClick: function() {
return false;
}
},
dataLabels: {
enabled: !(this.data.length > 1),
align: 'left',
inside: true,
formatter: function() {
return this.point.name;
},
style: {
color: '#000',
fontWeight: 'normal',
textTransform: 'uppercase',
opacity: '1',
textOutline: false
}
}
}
}
};
}
dataChanged() {
this.updateChart();
}
}

View File

@ -0,0 +1,11 @@
<template>
<a class="${direction}"
href="javascript:void(0)"
role="button"
data-slide="${slide}"
data-target="#${carousel.carouselNode.id}">
<span>${linkTextBefore}</span>
<img src="${image}" alt="${linkText}"/>
<span>${linkTextAfter}</span>
</a>
</template>

View File

@ -0,0 +1,33 @@
import {bindable, inject} from 'aurelia-framework';
import {FtCarousel} from './ft-carousel';
import environment from '../../../../environment';
//import {LogManager} from 'aurelia-framework';
//const logger = LogManager.getLogger('ft-carousel');
@inject(FtCarousel)
export class FtCarouselNavigation {
@bindable direction;
@bindable linkText;
leftLinkText;
rightLinkText;
image;
constructor(carousel) {
this.carousel = carousel;
//logger.debug(carousel);
this.leftLinkText = carousel.leftLinkText;
this.rightLinkText = carousel.rightLinkText;
}
attached() {
if (this.direction === 'left') {
this.image = `${environment.cssRoot}/assets/img/carousel-navb-l.png`;
this.linkTextAfter = this.leftLinkText;
this.slide = 'prev';
} else {
this.image = `${environment.cssRoot}/assets/img/carousel-navb-r.png`;
this.linkTextBefore = this.rightLinkText;
this.slide = 'next';
}
}
}

View File

@ -0,0 +1,41 @@
<template>
<!--<require from="./ft-carousel-navigation"></require>-->
<div class="col-sm-12" data-fti-component="carousel">
<div id="ft-carousel-header" class="carousel-header">
${currentSlideTitle}
</div>
<!-- Left and right controls -->
<div class="nav-carousel">
<!--<ft-carousel-navigation direction="left" link-text.bind="leftLinkText"></ft-carousel-navigation>-->
<!--<ft-carousel-navigation direction="right" link-text.bind="rightLinkText"></ft-carousel-navigation>-->
<a class="left"
href="javascript:void(0)"
role="button"
data-slide="prev"
data-target="#${carouselNode.id}">
<img src="${imageL}" alt="${leftLinkText}"/>
<span>${leftLinkText}</span>
</a>
<a class="right"
href="javascript:void(0)"
role="button"
data-slide="next"
data-target="#${carouselNode.id}">
<span>${rightLinkText}</span>
<img src="${imageR}" alt="${rightLinkText}"/>
</a>
</div>
<div ref="carouselNode" class="carousel slide" data-ride="carousel" data-interval="false">
<!-- slide content added through tranclusion -->
<slot></slot>
<!-- Indicators -->
<ol class="carousel-indicators ft-carousel-indicator">
<li repeat.for="1 of numberOfSlides"
data-target="#${carouselNode.id}"
data-slide-to="${$index}"
class="${($index === 0) ? 'active' : 'inactive'}"></li>
</ol>
</div>
</div>
</template>

View File

@ -0,0 +1,100 @@
import {bindable} from 'aurelia-framework';
import environment from '../../../../environment';
//import {LogManager} from 'aurelia-framework';
//const logger = LogManager.getLogger('ft-carousel');
let index = 0;
/**
* Returns array with slides titles
* @param {Object} carousel
* @returns {Array}
*/
function getSlidesTitle(carousel) {
let carouselTitlesArr = [];
let carouselTitles = carousel.find('.item');
for (let i = 0; i < carouselTitles.length; i++) {
carouselTitlesArr.push(carouselTitles[i].getAttribute('slide-title'));
}
return carouselTitlesArr;
}
/**
* Returns current slide position in array returned by getSlidesTitle
* @param {Array} carouselTitlesArr
* @param {String} currentSlideTitle
* @returns {Number}
*/
function getNextSlideNumber(carouselTitlesArr, currentSlideTitle) {
let slideNr = 0;
for (let carouselTitle of carouselTitlesArr) {
if (carouselTitle === currentSlideTitle) {
return slideNr;
}
slideNr++;
}
}
/**
* Returns Next Slide Title
* @param {Array} carouselTitlesArr
* @param {int} slideNR
* @returns {Array}
*/
function getNextSlide(carouselTitlesArr, slideNR) {
let nrOfSlides = carouselTitlesArr.length;
let nextSlideNR = parseInt(slideNR, 10) + 1;
if (nextSlideNR > nrOfSlides - 1) nextSlideNR = 0;
return carouselTitlesArr[nextSlideNR];
}
/**
* Returns Previous Slide Title
* @param {Array} carouselTitlesArr
* @param {int} slideNR
* @returns {Array}
*/
function getPrevSlide(carouselTitlesArr, slideNR) {
let nrOfSlides = carouselTitlesArr.length;
let prevSlideNR = parseInt(slideNR, 10) - 1;
if (prevSlideNR < 0) prevSlideNR = nrOfSlides - 1;
return carouselTitlesArr[prevSlideNR];
}
export class FtCarousel {
@bindable leftLinkText;
@bindable rightLinkText;
currentSlideTitle = '';
numberOfSlides = 0;
attached() {
this.carouselNode.id = `carousel-${++index}`;
let carousel = $(this.carouselNode);
let carouselTitles = getSlidesTitle(carousel);
//logger.debug(carouselTitles);
carousel.bind('slid.bs.carousel', (e) => {
let currentSlideTitle = e.relatedTarget.getAttribute('slide-title');
let slideNR = getNextSlideNumber(carouselTitles, currentSlideTitle);
let nextSlide = getNextSlide(carouselTitles, slideNR);
let prevSlide = getPrevSlide(carouselTitles, slideNR);
this.currentSlideTitle = currentSlideTitle;
this.leftLinkText = prevSlide;
this.rightLinkText = nextSlide;
//logger.debug('Previous Slide: ' + prevSlide + '; Next Slide: ' + nextSlide);
});
let slides = carousel.find('.item');
let currentSlideTitle = slides[0].getAttribute('slide-title');
let slideNR = getNextSlideNumber(carouselTitles, currentSlideTitle);
let nextSlide = getNextSlide(carouselTitles, slideNR);
let prevSlide = getPrevSlide(carouselTitles, slideNR);
this.numberOfSlides = slides.length;
this.currentSlideTitle = currentSlideTitle;
this.leftLinkText = prevSlide;
this.rightLinkText = nextSlide;
this.imageL = `${environment.cssRoot}/assets/img/carousel-navb-l.png`;
this.imageR = `${environment.cssRoot}/assets/img/carousel-navb-r.png`;
//logger.debug('currentSlideTitle: ' + this.currentSlideTitle);
//logger.debug('Previous Slide: ' + prevSlide + '; Next Slide: ' + nextSlide);
carousel.carousel();
}
}

View File

@ -0,0 +1,15 @@
# ft-carousel
This uses the bootstrap carousel. The carousel is created in the attached() method (where jquery is available.
A unique id is created using a timestamp and passed to the data-target attributes of the navigation elements.
The element that we are going to create the carousel on is passed to the component using ```ref```.
The actual content of each slide is passed in using *transclusion* ie a slot. It is assumed that each slide will be
enclosed in a div with a ```item``` class and a ```slide-title``` attribute.
The next and previous buttons are added through sub-elements ```ft-carousel-navigation```. These elements gain a
reference to the parent through dependency injection, but initialisation parameters for labels and whether left or right
are passed through bindings.

View File

@ -0,0 +1 @@
<template class="ft-chart chart"></template>

View File

@ -0,0 +1,117 @@
import {
bindable,
inject
} from 'aurelia-framework';
import merge from 'lodash/merge';
import isArray from 'lodash/isArray';
import isObjectLike from 'lodash/isObjectLike';
// import {LogManager} from 'aurelia-framework';
// const logger = LogManager.getLogger( 'ft-chart' );
@inject( Element )
export class FtChart {
@bindable options;
@bindable series = [];
chart;
newChartOnUpdate = false; // determines which update animation method to use - depends on data which method looks best
defaultOptions = {
credits: {
enabled: false
},
exporting: {
enabled: false
},
legend: {
enabled: false
},
title: {
text: ''
}
};
constructor( element ) {
this.element = element;
}
attached() {
let newOpts = this.mergeOptions();
newOpts.series = this._getSeriesAsArray();
this.chart = Highcharts.chart( this.element, newOpts );
$( '.tab-pane' ).attrchange( {
trackValues: true,
callback: e => {
// logger.debug( this.chart );
this.updateChart();
// this.chart.reflow();
return;
}
} );
}
mergeOptions() {
// this can be overriden in sub-classes
let newOpts = merge( {}, this.defaultOptions, this.options );
return newOpts;
}
updateChart() {
if ( this.chart ) {
/*
Update Options.
Do this before updating the series, so it doesn't block the animation
*/
let newOpts = this.mergeOptions();
let newSeriesArr = this._getSeriesAsArray();
if ( this.newChartOnUpdate ) {
// this animation update method destroys the old one and draws a new chart from scratch
if ( this.chart ) {
newOpts.series = newSeriesArr;
this.chart.destroy();
this.chart = Highcharts.chart( this.element, newOpts );
}
} else {
/*
Merge in new series.
Rather than delete data, and start again, update existing series data, so that chart re-animates
*/
this.chart.update( newOpts );
if ( this.chart && this.chart.series ) {
this.chart.series.forEach( s => {
if ( newSeriesArr.length ) {
s.setData( newSeriesArr.shift().data );
} else {
s.remove();
}
} );
if ( newSeriesArr.length ) {
newSeriesArr.forEach( s => {
this.chart.addSeries( s );
} );
}
}
}
}
}
optionsChanged() {
this.updateChart();
}
seriesChanged() {
this.updateChart();
}
// accomodate single series i.e. {} in addition to []
_getSeriesAsArray() {
if ( isArray( this.series ) ) {
return this.series.slice(); // take a copy of series, so we're safe to make changes
} else if ( isObjectLike( this.series ) ) {
return [ Object.assign( {}, this.series ) ];
}
return;
}
}

View File

@ -0,0 +1,103 @@
import {
bindable,
useView,
computedFrom
} from 'aurelia-framework';
import {FtChart} from './ft-chart';
// import {LogManager} from 'aurelia-framework';
// const logger = LogManager.getLogger('ft-chart');
@useView( './ft-chart.html' )
export class FtColumnChart extends FtChart {
// @bindable options;
@bindable data = [];
@bindable suffix = '';
@bindable prefix = '';
@bindable type = '';
@bindable categories = '';
@bindable ytitle = '';
@computedFrom( 'data', 'suffix', 'prefix', 'type', 'categories', 'ytitle' )
get series() {
return this.data;
}
newChartOnUpdate = true;
get options() {
let suffix = this.suffix;
let prefix = this.prefix;
let type = this.type;
let categories = this.categories;
let ytitle = this.ytitle;
let ytitleEnabled = ( ytitle !== '' ) ? true : false;
let _self = this;
return {
chart: {
colorByPoint: true,
type: type
// width: $('.tab-pane.active').width()
},
colors: [
'#005598', '#4e9d2d', '#00a0dc', '#333333', '#ed7700',
'#8a1811', '#163758', '#81bb00', '#5fc0eb', '#767676',
'#f7bb00', '#cb0000', '#bad583', '#a5d7f5', '#a7a7a7',
'#f5db8b', '#d3e3b0', '#DBEFFB', '#c9c9c9', '#e8e8e8'
],
tooltip: {
formatter: function() {
if ( _self.data.length > 1 ) {
return `${this.point.name}<br />${prefix}${Highcharts.numberFormat(this.point.y, 2)}${suffix}`;
}
return `${this.point.name}<br />${prefix}${Highcharts.numberFormat(this.point.y, 2)}${suffix}`;
}
},
xAxis: {
categories: categories
},
yAxis: {
title: {
enabled: ytitleEnabled,
text: ytitle
},
labels: {
useHTML: true,
formatter: function() {
return `${prefix}${Highcharts.numberFormat(this.value, 1)}${suffix}`;
}
}
},
legend: {
enabled: false,
align: 'left',
verticalAlign: 'top',
layout: 'horizontal',
x: 0,
symbolRadius: 0,
itemHoverStyle: {
cursor: 'default'
}
},
plotOptions: {
series: {
events: {
legendItemClick: function() {
return false;
}
},
animation: {
duration: 2000,
easing: 'easeIn'
},
dataLabels: {
enabled: false
}
}
}
};
}
dataChanged() {
this.updateChart();
}
}

View File

@ -0,0 +1,24 @@
<template bindable="data">
<table class="table table-text" if.bind="!data.empty">
<thead>
<tr>
<th>
<i class="chart-key chart-color-${data.index}"></i>
</th>
<th>
<strong innerHTML.bind="data.label"></strong>
</th>
<th class="text-right">
<ft-label label.bind="data.total | ftWithPercentOrDash"></ft-label>
</th>
</tr>
</thead>
<tbody if.bind="!data.hideRows">
<tr repeat.for="row of data.items">
<td >${row.name}</td>
<td >${row.val | ftWithPercentOrDash}</td>
</tr>
</tbody>
</table>
</template>

View File

@ -0,0 +1,27 @@
<template>
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="date-selector form-inline clearfix">
<div class="date-picker date-dropdown input-group hasDatepicker" id="dp1490008761101">
<div class="ui-datepicker-inline ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" style="display: block;">
<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix ui-corner-all">
<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click" title="Prev"><span class="ui-icon ui-icon-circle-triangle-w">Prev</span></a><a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="Next"><span class="ui-icon ui-icon-circle-triangle-e">Next</span></a>
<div class="ui-datepicker-title">
<select class="ui-datepicker-month" if.bind="dateFormat==='M-Y'" value.bind="selectedMnth">
<option value.bind="availableMonths" repeat.for="month of availableMonths" value="${ month.key }">${ month.value }</option>
<select>
<select class="ui-datepicker-year" change.delegate="yearChange()" value.bind="selectedYr">
<option repeat.for="year of availableYears" value="${ year.year }">${ year.year }</option>
</select>
</div>
</div>
</div>
</div>
<button class="btn btn-primary btn-show" if.bind="submitType==='buttons'" click.delegate="yearChange('button')">Show Data</button>
<button class="btn btn-default btn-disabled" if.bind="submitType==='buttons'" click.delegate="resetToCurrent()">Reset to Current</button>
</div>
</div>
</div>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More