Browse Source

Add UIO options, test.

pull/3/head
Ned Zimmerman 7 years ago
parent
commit
6242b6fc55
No known key found for this signature in database
GPG Key ID: FF56334A013120CA
  1. 5
      app/filters.php
  2. 11
      app/helpers.php
  3. 7
      app/setup.php
  4. 6
      composer.json
  5. 407
      composer.lock
  6. 227
      lib/infusion/Infusion-LICENSE.txt
  7. 212
      lib/infusion/README.md
  8. 344
      lib/infusion/ReleaseNotes.md
  9. 30
      lib/infusion/infusion-uiOptions.js
  10. 1
      lib/infusion/infusion-uiOptions.js.map
  11. 171
      lib/infusion/src/components/slidingPanel/js/SlidingPanel.js
  12. 16
      lib/infusion/src/components/slidingPanel/slidingPanelDependencies.json
  13. 3
      lib/infusion/src/components/tableOfContents/css/TableOfContents.css
  14. 32
      lib/infusion/src/components/tableOfContents/html/TableOfContents.html
  15. 377
      lib/infusion/src/components/tableOfContents/js/TableOfContents.js
  16. 15
      lib/infusion/src/components/tableOfContents/tableOfContentsDependencies.json
  17. 113
      lib/infusion/src/components/textToSpeech/js/MockTTS.js
  18. 288
      lib/infusion/src/components/textToSpeech/js/TextToSpeech.js
  19. 14
      lib/infusion/src/components/textToSpeech/textToSpeechDependencies.json
  20. 312
      lib/infusion/src/components/textfieldSlider/js/TextfieldSlider.js
  21. 18
      lib/infusion/src/components/textfieldSlider/textfieldSliderDependencies.json
  22. 45
      lib/infusion/src/components/uiOptions/js/UIOptions.js
  23. 14
      lib/infusion/src/components/uiOptions/uiOptionsDependencies.json
  24. 69
      lib/infusion/src/framework/core/css/fluid.css
  25. 132
      lib/infusion/src/framework/core/css/fluidDebugging.css
  26. 26
      lib/infusion/src/framework/core/frameworkDependencies.json
  27. BIN
      lib/infusion/src/framework/core/images/debug_tab.png
  28. BIN
      lib/infusion/src/framework/core/images/magnifying_glass.png
  29. 1495
      lib/infusion/src/framework/core/js/DataBinding.js
  30. 2806
      lib/infusion/src/framework/core/js/Fluid.js
  31. 116
      lib/infusion/src/framework/core/js/FluidDOMUtilities.js
  32. 300
      lib/infusion/src/framework/core/js/FluidDebugging.js
  33. 191
      lib/infusion/src/framework/core/js/FluidDocument.js
  34. 2458
      lib/infusion/src/framework/core/js/FluidIoC.js
  35. 266
      lib/infusion/src/framework/core/js/FluidPromises.js
  36. 439
      lib/infusion/src/framework/core/js/FluidRequests.js
  37. 693
      lib/infusion/src/framework/core/js/FluidView.js
  38. 663
      lib/infusion/src/framework/core/js/FluidViewDebugging.js
  39. 117
      lib/infusion/src/framework/core/js/JavaProperties.js
  40. 675
      lib/infusion/src/framework/core/js/ModelTransformation.js
  41. 743
      lib/infusion/src/framework/core/js/ModelTransformationTransforms.js
  42. 73
      lib/infusion/src/framework/core/js/ResourceLoader.js
  43. 623
      lib/infusion/src/framework/core/js/jquery.keyboard-a11y.js
  44. 152
      lib/infusion/src/framework/core/js/jquery.standalone.js
  45. 4
      lib/infusion/src/framework/enhancement/css/ProgressiveEnhancement.css
  46. 14
      lib/infusion/src/framework/enhancement/enhancementDependencies.json
  47. 221
      lib/infusion/src/framework/enhancement/js/ContextAwareness.js
  48. 34
      lib/infusion/src/framework/enhancement/js/ProgressiveEnhancement.js
  49. 1
      lib/infusion/src/framework/preferences/css/Enactors.css
  50. 1
      lib/infusion/src/framework/preferences/css/FullNoPreviewPrefsEditor.css
  51. 1
      lib/infusion/src/framework/preferences/css/FullPrefsEditor.css
  52. 1
      lib/infusion/src/framework/preferences/css/FullPreviewPrefsEditor.css
  53. 1
      lib/infusion/src/framework/preferences/css/PrefsEditor.css
  54. 1
      lib/infusion/src/framework/preferences/css/SeparatedPanelPrefsEditor.css
  55. 1
      lib/infusion/src/framework/preferences/css/SeparatedPanelPrefsEditorFrame.css
  56. 42
      lib/infusion/src/framework/preferences/css/stylus/Enactors.styl
  57. 47
      lib/infusion/src/framework/preferences/css/stylus/FullNoPreviewPrefsEditor.styl
  58. 47
      lib/infusion/src/framework/preferences/css/stylus/FullPrefsEditor.styl
  59. 75
      lib/infusion/src/framework/preferences/css/stylus/FullPreviewPrefsEditor.styl
  60. 420
      lib/infusion/src/framework/preferences/css/stylus/PrefsEditor.styl
  61. 26
      lib/infusion/src/framework/preferences/css/stylus/README.md
  62. 97
      lib/infusion/src/framework/preferences/css/stylus/SeparatedPanelPrefsEditor.styl
  63. 110
      lib/infusion/src/framework/preferences/css/stylus/SeparatedPanelPrefsEditorFrame.styl
  64. 43
      lib/infusion/src/framework/preferences/css/stylus/utils/Helpers.styl
  65. 358
      lib/infusion/src/framework/preferences/css/stylus/utils/Themes.styl
  66. BIN
      lib/infusion/src/framework/preferences/fonts/InfusionIcons-PrefsEditor.eot
  67. BIN
      lib/infusion/src/framework/preferences/fonts/InfusionIcons-PrefsEditor.ttf
  68. 27
      lib/infusion/src/framework/preferences/html/FullNoPreviewPrefsEditor.html
  69. 29
      lib/infusion/src/framework/preferences/html/FullPreviewPrefsEditor.html
  70. 13
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-contrast.html
  71. 2
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-emphasizeLinks.html
  72. 2
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-inputsLarger.html
  73. 10
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-layout.html
  74. 12
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-lineSpace-jQueryUI.html
  75. 12
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-lineSpace-nativeHTML.html
  76. 5
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-linksControls.html
  77. 10
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-speak.html
  78. 6
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-textFont.html
  79. 15
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-textSize-jQueryUI.html
  80. 15
      lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-textSize-nativeHTML.html
  81. 12
      lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditor.html
  82. 34
      lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-jQueryUI.html
  83. 30
      lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-nativeHTML.html
  84. BIN
      lib/infusion/src/framework/preferences/images/default/separatedpanelbg.png
  85. 498
      lib/infusion/src/framework/preferences/js/AuxBuilder.js
  86. 208
      lib/infusion/src/framework/preferences/js/Builder.js
  87. 431
      lib/infusion/src/framework/preferences/js/Enactors.js
  88. 49
      lib/infusion/src/framework/preferences/js/FullNoPreviewPrefsEditor.js
  89. 80
      lib/infusion/src/framework/preferences/js/FullPreviewPrefsEditor.js
  90. 1034
      lib/infusion/src/framework/preferences/js/Panels.js
  91. 473
      lib/infusion/src/framework/preferences/js/PrefsEditor.js
  92. 129
      lib/infusion/src/framework/preferences/js/PrimaryBuilder.js
  93. 135
      lib/infusion/src/framework/preferences/js/SelfVoicingEnactor.js
  94. 39
      lib/infusion/src/framework/preferences/js/SelfVoicingPanel.js
  95. 69
      lib/infusion/src/framework/preferences/js/SelfVoicingSchemas.js
  96. 461
      lib/infusion/src/framework/preferences/js/SeparatedPanelPrefsEditor.js
  97. 409
      lib/infusion/src/framework/preferences/js/StarterGrades.js
  98. 325
      lib/infusion/src/framework/preferences/js/StarterSchemas.js
  99. 168
      lib/infusion/src/framework/preferences/js/Store.js
  100. 118
      lib/infusion/src/framework/preferences/js/UIEnhancer.js
  101. Some files were not shown because too many files have changed in this diff Show More

5
app/filters.php

@ -13,11 +13,6 @@ add_filter('body_class', function (array $classes) {
}
}
/** Add class if sidebar is active */
if (display_sidebar()) {
$classes[] = 'sidebar-primary';
}
/** Clean up class names for custom templates */
$classes = array_map(function ($class) {
return preg_replace(['/-blade(-php)?$/', '/^page-template-views/'], '', $class);

11
app/helpers.php

@ -127,14 +127,3 @@ function locate_template($templates)
{
return \locate_template(filter_templates($templates));
}
/**
* Determine whether to show the sidebar
* @return bool
*/
function display_sidebar()
{
static $display;
isset($display) || $display = apply_filters('sage/display_sidebar', false);
return $display;
}

7
app/setup.php

@ -18,7 +18,14 @@ add_action('wp_enqueue_scripts', function () {
null
);
wp_enqueue_style('aldine/main.css', asset_path('styles/main.css'), false, null);
wp_enqueue_style('uio/normalize.css', get_theme_file_uri() . '/lib/infusion/src/lib/normalize/css/normalize.css', false, null);
wp_enqueue_style('uio/fluid.css', get_theme_file_uri() . '/lib/infusion/src/framework/core/css/fluid.css', false, null);
wp_enqueue_style('uio/enactors.css', get_theme_file_uri() . '/lib/infusion/src/framework/preferences/css/Enactors.css', false, null);
wp_enqueue_style('uio/prefseditor.css', get_theme_file_uri() . '/lib/infusion/src/framework/preferences/css/PrefsEditor.css', false, null);
wp_enqueue_style('uio/separatedpanelprefseditor.css', get_theme_file_uri() . '/lib/infusion/src/framework/preferences/css/SeparatedPanelPrefsEditor.css', false, null);
wp_enqueue_script('aldine/main.js', asset_path('scripts/main.js'), ['jquery'], null, true);
wp_enqueue_script('uio.js', get_theme_file_uri() . '/lib/infusion/infusion-uiOptions.js', ['jquery'], null, true);
wp_localize_script('aldine/main.js', 'SAGE_DIST_PATH', get_theme_file_uri('dist/'));
}, 100);
/**

6
composer.json

@ -34,15 +34,11 @@
"soberwp/controller": "~9.0.0-beta.4"
},
"require-dev": {
"squizlabs/php_codesniffer": "^2.8.0",
"roots/sage-installer": "~1.3"
"squizlabs/php_codesniffer": "^2.8.0"
},
"scripts": {
"test": [
"phpcs"
],
"post-create-project-cmd": [
"Roots\\Sage\\Installer\\ComposerScript::postCreateProject"
]
}
}

407
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "dff0ad9351f0ed99a3ef9264d114d4f5",
"content-hash": "20e6a6348254da7e1d66757d1a89fc01",
"packages": [
{
"name": "brain/hierarchy",
@ -993,169 +993,6 @@
}
],
"packages-dev": [
{
"name": "illuminate/console",
"version": "v5.4.27",
"source": {
"type": "git",
"url": "https://github.com/illuminate/console.git",
"reference": "bdc5c6f53cb474e2aeec46b6a9999fcedfb62a4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/console/zipball/bdc5c6f53cb474e2aeec46b6a9999fcedfb62a4e",
"reference": "bdc5c6f53cb474e2aeec46b6a9999fcedfb62a4e",
"shasum": ""
},
"require": {
"illuminate/contracts": "5.4.*",
"illuminate/support": "5.4.*",
"nesbot/carbon": "~1.20",
"php": ">=5.6.4",
"symfony/console": "~3.2"
},
"suggest": {
"guzzlehttp/guzzle": "Required to use the ping methods on schedules (~6.0).",
"mtdowling/cron-expression": "Required to use scheduling component (~1.0).",
"symfony/process": "Required to use scheduling component (~3.2)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.4-dev"
}
},
"autoload": {
"psr-4": {
"Illuminate\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "The Illuminate Console package.",
"homepage": "https://laravel.com",
"time": "2017-06-10T13:11:18+00:00"
},
{
"name": "nesbot/carbon",
"version": "1.22.1",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc",
"reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/translation": "~2.6 || ~3.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2",
"phpunit/phpunit": "~4.0 || ~5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.23-dev"
}
},
"autoload": {
"psr-4": {
"Carbon\\": "src/Carbon/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "brian@nesbot.com",
"homepage": "http://nesbot.com"
}
],
"description": "A simple API extension for DateTime.",
"homepage": "http://carbon.nesbot.com",
"keywords": [
"date",
"datetime",
"time"
],
"time": "2017-01-16T07:55:07+00:00"
},
{
"name": "roots/sage-installer",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/roots/sage-installer.git",
"reference": "dca019cfa6f1f2cb2e155dfc6574829b80c689b7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/roots/sage-installer/zipball/dca019cfa6f1f2cb2e155dfc6574829b80c689b7",
"reference": "dca019cfa6f1f2cb2e155dfc6574829b80c689b7",
"shasum": ""
},
"require": {
"illuminate/console": "~5.4",
"illuminate/filesystem": "~5.4",
"symfony/process": "~3.3"
},
"require-dev": {
"squizlabs/php_codesniffer": "~3.0"
},
"bin": [
"bin/sage"
],
"type": "library",
"autoload": {
"psr-4": {
"Roots\\Sage\\Installer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ben Word",
"email": "ben@benword.com",
"homepage": "https://github.com/retlehs"
},
{
"name": "QWp6t",
"email": "hi@qwp6t.me",
"homepage": "https://github.com/qwp6t"
}
],
"description": "Sage installer.",
"keywords": [
"FontAwesome",
"bootstrap",
"foundation",
"sage",
"tachyons",
"theme",
"wordpress"
],
"time": "2017-08-12T00:16:45+00:00"
},
{
"name": "squizlabs/php_codesniffer",
"version": "2.9.1",
@ -1233,248 +1070,6 @@
"standards"
],
"time": "2017-05-22T02:43:20+00:00"
},
{
"name": "symfony/console",
"version": "v3.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201",
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"symfony/debug": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/dependency-injection": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.3",
"symfony/dependency-injection": "~3.3",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/filesystem": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/filesystem": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2017-07-29T21:27:59+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
"reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2017-06-14T15:44:48+00:00"
},
{
"name": "symfony/process",
"version": "v3.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2017-07-13T13:05:09+00:00"
},
{
"name": "symfony/translation",
"version": "v3.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3",
"reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/config": "<2.8",
"symfony/yaml": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.8|~3.0",
"symfony/intl": "^2.8.18|^3.2.5",
"symfony/yaml": "~3.3"
},
"suggest": {
"psr/log": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Translation\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2017-06-24T16:45:30+00:00"
}
],
"aliases": [],

227
lib/infusion/Infusion-LICENSE.txt

@ -0,0 +1,227 @@
Infusion is available under either the terms of the New BSD license or the
Educational Community License, Version 2.0. As a recipient of Infusion, you may
choose which license to receive this code under (except as noted in per-module
LICENSE files). All modules are Copyright 2011 OCAD University except
where noted otherwise in the code itself, or if the modules reside in a separate
directory, they may contain explicit declarations of copyright in both the
LICENSE file in the directory in which they reside and in the code itself. No
external contributions are allowed under licenses which are fundamentally
incompatible with the ECL or BSD licenses that Infusion is distributed under.
The text of the ECL and BSD licenses is reproduced below.
Educational Community License, Version 2.0
*************************************
Copyright 2011 OCAD University
Educational Community License, Version 2.0, April 2007
The Educational Community License version 2.0 ("ECL") consists of the Apache 2.0
license, modified to change the scope of the patent grant in section 3 to be
specific to the needs of the education communities using this license. The
original Apache 2.0 license can be found at:
http://www.apache.org/licenses/LICENSE-2.0
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed. Any
patent license granted hereby with respect to contributions by an individual
employed by an institution or organization is limited to patent claims where the
individual that is the author of the Work is also the inventor of the patent
claims licensed, and where the organization or institution has the right to
grant such license under applicable grant and research funding agreements. No
other express or implied licenses are granted.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
1. You must give any other recipients of the Work or Derivative Works a copy
of this License; and
2. You must cause any modified files to carry prominent notices stating that
You changed the files; and
3. You must retain, in the Source form of any Derivative Works that You
distribute, all copyright, patent, trademark, and attribution notices from
the Source form of the Work, excluding those notices that do not pertain to
any part of the Derivative Works; and
4. If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy of
the attribution notices contained within such NOTICE file, excluding those
notices that do not pertain to any part of the Derivative Works, in at least
one of the following places: within a NOTICE text file distributed as part of
the Derivative Works; within the Source form or documentation, if provided
along with the Derivative Works; or, within a display generated by the
Derivative Works, if and wherever such third-party notices normally appear.
The contents of the NOTICE file are for informational purposes only and do
not modify the License. You may add Your own attribution notices within
Derivative Works that You distribute, alongside or as an addendum to the
NOTICE text from the Work, provided that such additional attribution notices
cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS FOR ECL 2.0
The New BSD license
**********************
Copyright 2011 OCAD University
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of OCAD University nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
END OF TERMS AND CONDITIONS FOR THE NEW BSD LICENSE

212
lib/infusion/README.md

@ -0,0 +1,212 @@
## What Is Infusion? ##
Infusion is a different kind of JavaScript framework. Our approach is to leave you in control—it's your interface, using your markup, your way. Infusion is accessible and very, very configurable.
Infusion includes:
* an application framework for developing flexible stuff with JavaScript and jQuery
* a collection of accessible UI components
## Where Can I See Infusion Components? ##
<http://fluidproject.org/infusion.html>
## How Do I Get Infusion? ##
* [Download a Release](https://github.com/fluid-project/infusion/releases)
* [Install from NPM](https://www.npmjs.com/package/infusion)
* [Fork on GitHub](https://github.com/fluid-project/infusion)
See [How Do I Create an Infusion Package?](#how-do-i-create-an-infusion-package), for details on creating complete or custom packages of Infusion.
## Where is the Infusion Documentation? ##
Infusion has comprehensive documentation at <http://docs.fluidproject.org/infusion>.
## Who Makes Infusion, and How Can I Help? ##
The Fluid community is an international group of designers, developers, and testers who focus on a common mission: improving the user experience and accessibility of the open web.
The best way to join the Fluid Community is to jump into any of our community activities. Visit our [website](http://fluidproject.org/) for links to our mailing lists, chat room, wiki, etc.
## Where is Infusion Used? ##
Infusion is the cornerstone of a number of Fluid's own projects dedicated to supporting inclusive design on the Web. You can see some of them featured on our [Projects page](http://fluidproject.org/projects.html). Infusion is also used in a variety of third-party applications, which are listed on the [Infusion Integrations](https://wiki.fluidproject.org/display/fluid/Infusion+Integrations) wiki page.
## How Do I Create an Infusion Package? ##
For simplicity and performance reasons, you may wish to create a concatenated, minified file. However, such a file is often difficult to read. To address this, source maps for the minified file are automatically generated to make debugging easier.
### Source Maps ###
Source maps are supported in all of the major browsers: [Chrome](https://developer.chrome.com/devtools/docs/javascript-debugging#source-maps), [Firefox](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map),
[IE 11](https://msdn.microsoft.com/library/dn255007#source_maps), and Safari. To make use of them, enable source maps in your debugging environment, and ensure that the source maps are hosted adjacent to the file they are associated with.
#### Source Map Example ####
* From the command line, run `grunt` to create a build of Infusion
* All Infusion packages come with a source map for the concatenated JavaScript file
* In the Infusion package, modify one of the demos to replace the individual javascript includes with a reference to "infusion-all.js"
* The "infusion-all.js" includes a reference to the "infusion-all.js.map" file, which is assumed to be hosted as its sibling
* Open the demo in a browser
* In the browser's debugger ensure that source maps are enabled
* In Firefox open the debugger
* In the debugger options, ensure that "Show Original Sources" is enabled
* see [MDN: Use a source map](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map)
* In the debugger you should now be able to view and debug the individual JavaScript files as though they were included separately
### Dependencies ###
* [node.js](http://nodejs.org/)
* [grunt-cli](http://gruntjs.com/)
All other development dependencies will be installed by running the following from the project root:
```bash
npm install
```
### Package Types ###
#### Infusion All Build ####
Will include all of Infusion. The source files packaged along with the single concatenated js file will include all of the demos and unit tests. This is a good choice if you are trying to learn Infusion.
```bash
grunt
```
##### Custom Build #####
Will only include the modules you request, and all of their dependencies, minus any that are explicitly excluded. Unlike the "all" build, none of the demos or tests are included with a custom package.
```bash
grunt custom
```
### Build Options ###
#### --source ####
__value__: true (Boolean)
_the value can be omitted if --source is the last flag specified_
By default all packages are minified. This option will allow you to maintain the readable spacing and comments.
```bash
grunt --source=true
grunt custom --source=true
```
#### --include ####
__value__: "module(s)" (String)
_only available to custom packages_
The `--include` option takes in a comma-separated string of the [Modules](#modules) to be included in a custom package. If omitted, all modules will be included (demos and tests will not be included).
```bash
grunt custom --include="inlineEdit, uiOptions"
```
#### --exclude ####
__value__: "module(s)" (String)
_only available to custom packages_
The exclude option takes in a comma-separated string of the [Modules](#modules) to be excluded from a custom package. The `--exclude` option takes priority over `--include`.
```bash
grunt custom --exclude="jQuery"
grunt custom --include="framework" --exclude="jQuery"
```
#### --name ####
__value__: "custom suffix" (String)
_only available to custom packages_
By default, custom packages are given a name with the form _infusion-custom-<version>.zip_ and the concatenated js file is called _infusion-custom.js_. By supplying the `--name` option, you can replace "custom" with any other valid string you like.
```bash
# this produces infusion-myPackage.js
grunt custom --name="myPackage"
```
### Modules ###
#### Framework Modules ####
* enhancement
* framework
* preferences
* renderer
#### Component Modules ####
* inlineEdit
* overviewPanel
* pager
* progress
* reorderer
* slidingPanel
* tableOfContents
* tabs
* textfieldSlider
* textToSpeech
* tooltip
* uiOptions
* undo
* uploader
#### External Libraries ####
* fastXmlPull
* jQuery
* jQueryUI
* jQueryScrollToPlugin
* jQueryTouchPunchPlugin
* normalize
All of these libraries are already bundled within the Infusion image.
## How Do I Run Tests? ##
There are two options available for running tests. The first option involves using browsers installed on your computer and the second uses browsers available in a VM.
### Run Tests Using Browsers Installed On Your Computer ###
Using this option requires the installation of [Testem](https://github.com/testem/testem/#installation) and then running ``testem ci --file tests/testem.json`` in this directory. Any browsers that Testem finds on your platform will be launched sequentially with each browser running the full Infusion test suite. The results will be returned in your terminal in the [TAP](https://testanything.org/) format. You can use the ``testem launchers`` command to get a list of available browsers.
**Note:** Any browser launched will need to be focused and remain the active window. Some of the tests require focus, and will report errors if they are not focused.
### Run Tests Using Browsers Installed In a VM ###
A [Fedora VM](https://github.com/idi-ops/packer-fedora) can be automatically created using tools provided by the [Prosperity4All Quality Infrastructure](https://github.com/GPII/qi-development-environments/). After meeting the [QI development VM requirements](https://github.com/GPII/qi-development-environments/#requirements) the ``vagrant up`` command can be used to launch a VM which will contain Testem and several browsers. Typing ``grunt tests`` will run the Infusion tests in the VM and the results will be displayed in your terminal.
When this VM is first created Chrome and Firefox will be upgraded to the latest versions available in the Fedora and Google package repositories. The ``vagrant provision`` command can be used at a later time to trigger the browser upgrade and general VM provisioning mechanism.
The benefits of using a VM include the following:
* Does not require testem to be installed on the host machine
* Allows other applications on the host machine to have focus while the tests are run
## Developing with the Preferences Framework ##
Infusion is in the process of switching to use [Stylus](http://learnboost.github.io/stylus/) for CSS pre-processing.
CSS files for the Preferences Framework have been re-written in Stylus. Only Stylus files are pushed into the github repository.
For developing the Preferences Framework, run the following from the project root to compile Stylus files to CSS:
```bash
grunt buildStylus
```
A `watch` task using [grunt-contrib-watch](https://github.com/gruntjs/grunt-contrib-watch) is also supplied to ease Stylus development. This task launches a process that watches all Stylus files in the `src` directory and recompiles them when they are changed. This task can be run using the following command:
```bash
grunt watch:buildStylus
```

344
lib/infusion/ReleaseNotes.md

@ -0,0 +1,344 @@
# Release Notes for Fluid Infusion 2.0.0 #
[Fluid Project](http://fluidproject.org)
[Infusion Documentation](https://github.com/fluid-project/infusion-docs)
## What's New in 2.0.0? ##
See [API Changes from 1.5 to 2.0](http://docs.fluidproject.org/infusion/development/APIChangesFrom1_5To2_0.html) and [Deprecations in 1.5](http://docs.fluidproject.org/infusion/development/DeprecationsIn1_5.html) on the [Infusion Documentation](https://github.com/fluid-project/infusion-docs) site.
For a complete list of Fixes and Improvements see the [Version 2.0](https://issues.fluidproject.org/projects/FLUID/versions/10041) summary in the [JIRA](https://issues.fluidproject.org) issue tracker.
**Note:** Infusion 1.9 was not officially released, but is available as an official branch. It is is available on GitHub at [Infusion 1.9.x](https://github.com/fluid-project/infusion/tree/1.9.x). For a complete list of Fixes and Improvements see the [Version 1.9](https://issues.fluidproject.org/projects/FLUID/versions/10520) summary in the [JIRA](https://issues.fluidproject.org) issue tracker.
### New Features ###
* Constraint-based priorities, supported by `listeners`, `modelListeners`, `modelRelay`, `distributeOptions`, `contextAwareness`, and `components`. This allows the specific order of those items to be configured. (See: [Priorities](http://docs.fluidproject.org/infusion/development/Priorities.html))
* Context Awareness - and things it relies on:
* Global Instantiator
* Every Infusion component, regardless of how it is instantiated, ends up in a single-rooted tree of components
* This enables use of modern IoC features such as model relay and declarative event binding
* Enables use of the root distributeOptions context "/"
* Enables the removal of "demands blocks"
* Useful debugging tip: Watch `fluid.globalInstantiator` in your JS debugging tools to see the structure of your application and its tree.
* `fluid.notImplemented` function for implementing abstract grades
* [Lazy loading for UI Options](http://docs.fluidproject.org/infusion/development/UserInterfaceOptionsAPI.html#lazyload) and instructions for how to use the Preferences Framework with a [zero initial load time](http://docs.fluidproject.org/infusion/development/tutorial-prefsFrameworkMinimalFootprint/MinimalFootprint.html).
* This should assist in improving performance when using the Preferences Framework, particularly for resource intensive sites and applications
* Much faster invokers and boiled listeners (c. 60x faster)
* Support for using Infusion with npm for both Node.js and web-based projects.
* Provides a variety of prebuilt versions of Infusion in the module's `dist` directory.
* Source Maps are generated for the concatenated JavaScript files
* View oriented IoC debugging tools
* Including FluidViewDebugging.js on the page of any Infusion application gives you access to the _IoC View Inspector_. Click on the small cogwheel icon at the bottom right of the page to open a panel which shows the details of the view components and their grades, that are attached to DOM nodes in the browser pane. This interface works similarly to the _DOM Inspector_ familiar from modern web browsers, but is an experimental implementation with an engineer-level UI.
### Removal of Deprecated Features ###
* Manual lifecycle points finalInit, postInit, etc.
* Obsolete syntax for arguments, options, etc.
* `"autoInit"` grade
* Static and dynamic environments, replaced by Global Instantiator
* The old model component hierarchy and "old ChangeApplier" implementation
* `fluid.demands`
* No more distinction between fast and dynamic invokers
* Model Relay specific component grades have been removed, model relay now works with any model grade.
## Obtaining Infusion ##
* [Fork on GitHub](https://github.com/fluid-project/infusion)
* [Download a Build](https://github.com/fluid-project/infusion/releases)
* [NPM](https://www.npmjs.com/package/infusion)
You can create your own custom build of Infusion using the [grunt build script](README.md#how-do-i-create-an-infusion-package).
## Demos ##
Infusion ships with demos of all of the components in action. You can find them in the _**demos**_ folder in the release bundle or on our [build site](http://build.fluidproject.org/).
When running the demos on your local machine, a web server is recommended. Several of the demos make use of AJAX calls; which typically are not allowed by
the browser when run from the local file system.
## License ##
Fluid Infusion is licensed under both the ECL 2.0 and new BSD licenses.
More information is available in our [wiki](http://wiki.fluidproject.org/display/fluid/Fluid+Licensing).
## Third Party Software in Infusion ##
This is a list of publicly available software that is redistributed with Fluid Infusion,
categorized by license:
### Apache 2.0 ###
* [`fluid.load.scripts` is based on Jake Archibald's script loading example](http://www.html5rocks.com/en/tutorials/speed/script-loading/#toc-dom-rescue)
* [Open Sans Light font](http://www.google.com/fonts/specimen/Open+Sans)
### MIT License ###
* [Buzz v1.1.0](http://buzz.jaysalvat.com)
* [Foundation v6.2.3](http://foundation.zurb.com/index.html)
* [HTML5 Boilerplate v4.3](http://html5boilerplate.com/)
* [html5shiv v3.7.2](https://code.google.com/p/html5shiv/)
* [jQuery v3.1.0](http://jquery.com/)
* [jQuery Mockjax v2.2.1](https://github.com/jakerella/jquery-mockjax)
* [jQuery QUnit v1.12.0](http://qunitjs.com)
* [jQuery QUnit Composite v1.0.1](https://github.com/jquery/qunit-composite)
* [jQuery scrollTo v1.4.2](http://flesler.blogspot.com/2007/10/jqueryscrollto.html)
* [jQuery Touch Punch v0.2.2](http://touchpunch.furf.com/)
* [jQuery UI (Core; Interactions: draggable, resizable; Widgets: button, checkboxradio, controlgroup, dialog, mouse, slider, tabs, and tooltip) v1.12.1](http://ui.jquery.com/)
* [jquery.selectbox v0.5 (forked)](https://github.com/fluid-project/jquery.selectbox)
* [jquery.simulate](https://github.com/eduardolundgren/jquery-simulate)
* [Micro Clearfix](http://nicolasgallagher.com/micro-clearfix-hack/)
* [Normalize v4.1.1](https://necolas.github.io/normalize.css/)
### zlib/libpng License ###
* [fastXmlPull is based on XML for Script's Fast Pull Parser v3.1](http://wiki.fluidproject.org/display/fluid/Licensing+for+fastXmlPull.js)
## Documentation ##
Documentation and tutorials can found on the [Infusion Documentation](http://docs.fluidproject.org/infusion/development/) site.
## Supported Browsers ##
Infusion 2.0 was tested with the following browsers:
* Chrome current (version 54)
* Firefox current (versions 49-50)
* Internet Explorer (version 11)
* Microsoft Edge (version 38)
* Safari (version 10)
Additional testing for mobile devices was performed with the following:
* Chrome (Android 6.0.1)
* Safari (iOS 10.1.1)
For more information see the [Fluid Infusion browser support](https://wiki.fluidproject.org/display/fluid/Prior+Browser+Support) wiki page.
### Testing Configurations ####
<table>
<summary>Testing Configurations</summary>
<thead>
<tr>
<th rowspan="2">Testing Task</th>
<th colspan="5">Desktop Browser</th>
<th colspan="2">Mobile Browser</th>
</tr>
<tr>
<th>Chrome</th>
<th>Firefox</th>
<th>IE 11</th>
<th>MS Edge</th>
<th>Safari</th>
<th>Chrome for Android</th>
<th>Safari iOS</th>
</tr>
</thead>
<tbody>
<tr>
<th>Run All Unit Tests</th>
<td>Chrome 54 (macOS 10.12)</td>
<td>Firefox 49 (macOS 10.12)</td>
<td>IE 11 (Win 10)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>Chrome 54 (Android 6.0.1 & 7.0.0)</td>
<td>Safari (iOS 10.1.1)</td>
</tr>
<tr>
<th>Smoke Tests - All Manual Tests</th>
<td>Chrome 54 (macOS 10.11.6)</td>
<td>Firefox 50 (macOS 10.12.1)</td>
<td>IE 11 (Win 8.1)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12.1)</td>
<td>Chrome 54 (Android 6.0.1)</td>
<td>Safari (iOS 10.1.1)</td>
</tr>
<tr>
<th>Smoke Tests - All Demos</th>
<td>Chrome 54 (macOS 10.12.1)</td>
<td>Firefox 50 (macOS 10.12.1)</td>
<td>IE 11 (Win 8.1)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12.1)</td>
<td>Chrome 54 (Android 6.0.1)</td>
<td>Safari (iOS 10.1.1)</td>
</tr>
<tr>
<th>Smoke Tests - All Examples</th>
<td>Chrome 54 (macOS 10.12.1)</td>
<td>Firefox 50 (macOS 10.12.1)</td>
<td>IE 11 (Win 8.1)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12.1)</td>
<td>Chrome 54 (Android 6.0.1)</td>
<td>Safari (iOS 10.1.1)</td>
</tr>
<tr>
<th>Inline Edit QA Test Plan - Simple Text</th>
<td>Chrome 54 (macOS 10.10)</td>
<td>Firefox 49 (openSUSE Linux 42.1)</td>
<td>IE 11 (Win 8.1)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Keyboard Accessibility QA Test Plan</th>
<td>Chrome 54 (Win 10)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 8.1)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Pager QA Test Plan</th>
<td>Chrome 54 (Win 10)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Progress QA Test Plan</th>
<td>Chrome 54 (macOS 10.11.6)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Reorderer QA Test Plan - Image Reorderer</th>
<td>Chrome 54 (Win 10)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Reorderer QA Test Plan - Layout Reorderer</th>
<td>Chrome 54 (macOS 10.11.6)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Reorderer QA Test Plan - List Reorderer</th>
<td>Chrome 54 (macOS 10.11.6)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Reorderer QA Test Plan - Grid Reorderer</th>
<td>Chrome 54 (macOS 10.11.6)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Preferences Framework QA Test Plan</th>
<td>Chrome 54 (Win 10)</td>
<td>Firefox 49.0.2 (macOS 10.12)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>UI Options QA Test Plan - Separated Panel</th>
<td>Chrome 54 (Win 10)</td>
<td>Firefox 49.0.2 (Win 10)</td>
<td>IE 11 (Win 7)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
<tr>
<th>Uploader QA Test Plan</th>
<td>Chrome 54 (macOS 10.12.1)</td>
<td>Firefox 49.0.2 (macOS 10.12.1)</td>
<td>IE 11 (Win 10)</td>
<td>MS Edge 38 (Win 10)</td>
<td>Safari 10 (macOS 10.12.1)</td>
<td>N/A</td>
<td>N/A</td>
</tr>
</tbody>
</table>
## Known Issues ##
The Fluid Project uses a [JIRA](http://issues.fluidproject.org) website to track bugs. Some of the known issues in this release are described here:
### Framework ###
* [FLUID-5912: "{arguments}" IoC references in dynamicComponents model block are incorrectly interpreted as implicit model relays](https://issues.fluidproject.org/browse/FLUID-5912)
* [FLUID-5546: Framework fails to deregister listeners to events which are injected from other components](https://issues.fluidproject.org/browse/FLUID-5546)
* [FLUID-5519: Timing of "initial transaction" in new model relay system is problematic](https://issues.fluidproject.org/browse/FLUID-5519)
### Inline Edit ###
* [FLUID-5392: Two clicks required to edit an empty inline edit field](https://issues.fluidproject.org/browse/FLUID-5392)
* [FLUID-1600: Pressing the "Tab" key to exit edit mode places focus on the wrong item](http://issues.fluidproject.org/browse/FLUID-1600)
### Layout Reorderer ###
* [FLUID-3864: Layout Reorderer failed to move portlets back to the first column in three-columns view with keyboard](http://issues.fluidproject.org/browse/FLUID-3864)
* [FLUID-3089: If columns become stacked, can't drag item into lower column](http://issues.fluidproject.org/browse/FLUID-3089)
### Pager ###
[FLUID-6081: VoiceOver on Pager doesn't announce the page number when the focus is on a page link](https://issues.fluidproject.org/browse/FLUID-6081)
### Reorderer ###
* [FLUID-6013: The Grid Reorderer and Image Reorderer are missing ARIA role=row containers](https://issues.fluidproject.org/browse/FLUID-6013)
* [FLUID-5870: Reorderer demo failures on IE 11](https://issues.fluidproject.org/browse/FLUID-5870)
* [FLUID-44737: Focus styling persists after moving focus from Reorderer](https://issues.fluidproject.org/browse/FLUID-4437)
* [FLUID-3925: With no wrapping on, the keyboard movement keystrokes are captured by the browser where a wrap would have occurred.](http://issues.fluidproject.org/browse/FLUID-3925)
### UI Options / Preferences Framework ###
* [FLUID-5928: Schema and Grade version save preferences to different values](https://issues.fluidproject.org/browse/FLUID-5928)
* [FLUID-5372: Increasing font size does not increase width of UIO panel](https://issues.fluidproject.org/browse/FLUID-5372)
* [FLUID-5223: If there's exactly one text field in the prefs editor, pressing enter on most inputs causes the form to submit](http://issues.fluidproject.org/browse/FLUID-5223)
* [FLUID-5218: Prefs editor requires iFrame template to be in the same place as panel templates; it probably shouldn't](http://issues.fluidproject.org/browse/FLUID-5218)
* [FLUID-5066: UIO Integrators shouldn't have to edit Infusion's copy of html templates to add panels, css](http://issues.fluidproject.org/browse/FLUID-5066)
* [FLUID-4491: Line spacing doesn't affect elements that have a line-height style set](http://issues.fluidproject.org/browse/FLUID-4491)
* [FLUID-4394: Separated Panel UI Options' iFrame HTML page (SeparatedPanelFrame.html) doesn't play nice with a concatenated build of Infusion](http://issues.fluidproject.org/browse/FLUID-4394)
### Undo ###
* [FLUID-3697: Undo hard-codes selector classes instead of using user-configured values](http://issues.fluidproject.org/browse/FLUID-3697)
### Uploader ###
* [FLUID-6079: Uploader error, when chosen files are too large, is not read by screenreader](https://issues.fluidproject.org/browse/FLUID-6079)
* [FLUID-6065: The focus remains on the "Browse Files" button with 2 keyboard tabbings in IE 11 and IE Edge](https://issues.fluidproject.org/browse/FLUID-6065)
* [FLUID-6045: The table header scrolls out of view as the file queue is scrolled](https://issues.fluidproject.org/browse/FLUID-6045)
* [FLUID-5737: Uploading size is higher than total size](https://issues.fluidproject.org/browse/FLUID-5737)
* [FLUID-4726: Cannot change uploader's button text through the string options.](https://issues.fluidproject.org/browse/FLUID-4726)

30
lib/infusion/infusion-uiOptions.js

File diff suppressed because one or more lines are too long

1
lib/infusion/infusion-uiOptions.js.map

File diff suppressed because one or more lines are too long

171
lib/infusion/src/components/slidingPanel/js/SlidingPanel.js

@ -0,0 +1,171 @@
/*
Copyright 2011-2015 OCAD University
Copyright 2011 Lucendo Development Ltd.
Copyright 2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/**********************
* Sliding Panel *
*********************/
fluid.defaults("fluid.slidingPanel", {
gradeNames: ["fluid.viewComponent"],
selectors: {
panel: ".flc-slidingPanel-panel",
toggleButton: ".flc-slidingPanel-toggleButton",
toggleButtonLabel: ".flc-slidingPanel-toggleButton"
},
strings: {
showText: "show",
hideText: "hide",
panelLabel: "panel"
},
events: {
onPanelHide: null,
onPanelShow: null,
afterPanelHide: null,
afterPanelShow: null
},
listeners: {
"onCreate.bindClick": {
"this": "{that}.dom.toggleButton",
"method": "click",
"args": ["{that}.togglePanel"]
},
"onCreate.bindModelChange": {
listener: "{that}.applier.modelChanged.addListener",
args: ["isShowing", "{that}.refreshView"]
},
"onCreate.setAriaProps": "{that}.setAriaProps",
"onCreate.setInitialState": {
listener: "{that}.refreshView"
},
"onPanelHide.setText": {
"this": "{that}.dom.toggleButtonLabel",
"method": "text",
"args": ["{that}.options.strings.showText"],
"priority": "first"
},
"onPanelHide.setAriaLabel": {
"this": "{that}.dom.toggleButtonLabel",
"method": "attr",
"args": ["aria-label", "{that}.options.strings.showTextAriaLabel"]
},
"onPanelShow.setText": {
"this": "{that}.dom.toggleButtonLabel",
"method": "text",
"args": ["{that}.options.strings.hideText"],
"priority": "first"
},
"onPanelShow.setAriaLabel": {
"this": "{that}.dom.toggleButtonLabel",
"method": "attr",
"args": ["aria-label", "{that}.options.strings.hideTextAriaLabel"]
},
"onPanelHide.operate": {
listener: "{that}.operateHide"
},
"onPanelShow.operate": {
listener: "{that}.operateShow"
},
"onCreate.setAriaStates": "{that}.setAriaStates"
},
members: {
panelId: {
expander: {
// create an id for panel
// and set that.panelId to the id value
funcName: "fluid.allocateSimpleId",
args: "{that}.dom.panel"
}
}
},
model: {
isShowing: false
},
modelListeners: {
"isShowing": {
funcName: "{that}.setAriaStates",
excludeSource: "init"
}
},
invokers: {
operateHide: {
"this": "{that}.dom.panel",
"method": "slideUp",
"args": ["{that}.options.animationDurations.hide", "{that}.events.afterPanelHide.fire"]
},
operateShow: {
"this": "{that}.dom.panel",
"method": "slideDown",
"args": ["{that}.options.animationDurations.show", "{that}.events.afterPanelShow.fire"]
},
hidePanel: {
func: "{that}.applier.change",
args: ["isShowing", false]
},
showPanel: {
func: "{that}.applier.change",
args: ["isShowing", true]
},
setAriaStates: {
funcName: "fluid.slidingPanel.setAriaStates",
args: ["{that}", "{that}.model.isShowing"]
},
setAriaProps: {
funcName: "fluid.slidingPanel.setAriaProperties",
args: ["{that}", "{that}.panelId"]
},
togglePanel: {
funcName: "fluid.slidingPanel.togglePanel",
args: ["{that}"]
},
refreshView: {
funcName: "fluid.slidingPanel.refreshView",
args: ["{that}"]
}
},
animationDurations: {
hide: 400,
show: 400
}
});
fluid.slidingPanel.togglePanel = function (that) {
that.applier.change("isShowing", !that.model.isShowing);
};
fluid.slidingPanel.refreshView = function (that) {
that.events[that.model.isShowing ? "onPanelShow" : "onPanelHide"].fire();
};
// panelId is passed in to ensure that it is evaluated before this
// function is called.
fluid.slidingPanel.setAriaProperties = function (that, panelId) {
that.locate("toggleButton").attr({
"role": "button",
"aria-controls": panelId
});
that.locate("panel").attr({
"aria-label": that.options.strings.panelLabel,
"role": "group"
});
};
fluid.slidingPanel.setAriaStates = function (that, isShowing) {
that.locate("toggleButton").attr("aria-pressed", isShowing);
that.locate("panel").attr("aria-expanded", isShowing);
};
})(jQuery, fluid_2_0_0);

16
lib/infusion/src/components/slidingPanel/slidingPanelDependencies.json

@ -0,0 +1,16 @@
{
"slidingPanel": {
"name": "Sliding Panel",
"description": "A widget that slides a panel in and out of view.",
"cssFiles": [],
"files": [
"./js/SlidingPanel.js"
],
"dependencies": [
"jQuery",
"jQueryUI",
"normalize",
"framework"
]
}
}

3
lib/infusion/src/components/tableOfContents/css/TableOfContents.css

@ -0,0 +1,3 @@
.fl-tableOfContents-hide-bullet {
list-style-type: none;
}

32
lib/infusion/src/components/tableOfContents/html/TableOfContents.html

@ -0,0 +1,32 @@
<!-- Table of contents template -->
<h2 class="flc-toc-header">Table of Contents Header</h2>
<ul class="flc-toc-levels-level1">
<li class="flc-toc-levels-items1">
<a class="flc-toc-levels-link1" href="">1</a>
<ul class="flc-toc-levels-level2">
<li class="flc-toc-levels-items2">
<a class="flc-toc-levels-link2" href="">2</a>
<ul class="flc-toc-levels-level3">
<li class="flc-toc-levels-items3">
<a class="flc-toc-levels-link3" href="">3</a>
<ul class="flc-toc-levels-level4">
<li class="flc-toc-levels-items4">
<a class="flc-toc-levels-link4" href="">4</a>
<ul class="flc-toc-levels-level5">
<li class="flc-toc-levels-items5">
<a class="flc-toc-levels-link5" href="">5</a>
<ul class="flc-toc-levels-level6">
<li class="flc-toc-levels-items6">
<a class="flc-toc-levels-link6" href="">6</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>

377
lib/infusion/src/components/tableOfContents/js/TableOfContents.js

@ -0,0 +1,377 @@
/*
Copyright 2011-2016 OCAD University
Copyright 2011 Lucendo Development Ltd.
Copyright 2012 Raising the Floor - US
Copyright 2015-2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/******
* ToC *
*******/
fluid.registerNamespace("fluid.tableOfContents");
fluid.tableOfContents.headingTextToAnchorInfo = function (heading) {
var id = fluid.allocateSimpleId(heading);
var anchorInfo = {
id: id,
url: "#" + id
};
return anchorInfo;
};
fluid.tableOfContents.locateHeadings = function (that) {
var headings = that.locate("headings");
fluid.each(that.options.ignoreForToC, function (sel) {
headings = headings.not(sel).not(sel + " :header");
});
return headings;
};
fluid.tableOfContents.refreshView = function (that) {
var headings = that.locateHeadings();
that.anchorInfo = fluid.transform(headings, function (heading) {
return that.headingTextToAnchorInfo(heading);
});
var headingsModel = that.modelBuilder.assembleModel(headings, that.anchorInfo);
that.applier.change("", headingsModel);
that.events.onRefresh.fire();
};
fluid.defaults("fluid.tableOfContents", {
gradeNames: ["fluid.viewComponent"],
components: {
levels: {
type: "fluid.tableOfContents.levels",
createOnEvent: "onCreate",
container: "{tableOfContents}.dom.tocContainer",
options: {
model: {
headings: "{tableOfContents}.model"
},
events: {
afterRender: "{tableOfContents}.events.afterRender"
},
listeners: {
"{tableOfContents}.events.onRefresh": "{that}.refreshView"
},
strings: "{tableOfContents}.options.strings"
}
},
modelBuilder: {
type: "fluid.tableOfContents.modelBuilder"
}
},
model: [],
invokers: {
headingTextToAnchorInfo: "fluid.tableOfContents.headingTextToAnchorInfo",
locateHeadings: {
funcName: "fluid.tableOfContents.locateHeadings",
args: ["{that}"]
},
refreshView: {
funcName: "fluid.tableOfContents.refreshView",
args: ["{that}"]
},
// TODO: is it weird to have hide and show on a component?
hide: {
"this": "{that}.dom.tocContainer",
"method": "hide"
},
show: {
"this": "{that}.dom.tocContainer",
"method": "show"
}
},
strings: {
tocHeader: "Table of Contents"
},
selectors: {
headings: ":header:visible",
tocContainer: ".flc-toc-tocContainer"
},
ignoreForToC: {
tocContainer: "{that}.options.selectors.tocContainer"
},
events: {
onRefresh: null,
afterRender: null,
onReady: {
events: {
"onCreate": "onCreate",
"afterRender": "afterRender"
},
args: ["{that}"]
}
},
listeners: {
"onCreate.refreshView": "{that}.refreshView"
}
});
/*******************
* ToC ModelBuilder *
********************/
fluid.registerNamespace("fluid.tableOfContents.modelBuilder");
fluid.tableOfContents.modelBuilder.toModel = function (headingInfo, modelLevelFn) {
var headings = fluid.copy(headingInfo);
var buildModelLevel = function (headings, level) {
var modelLevel = [];
while (headings.length > 0) {
var heading = headings[0];
if (heading.level < level) {
break;
}
if (heading.level > level) {
var subHeadings = buildModelLevel(headings, level + 1);
if (modelLevel.length > 0) {
modelLevel[modelLevel.length - 1].headings = subHeadings;
} else {
modelLevel = modelLevelFn(modelLevel, subHeadings);
}
}
if (heading.level === level) {
modelLevel.push(heading);
headings.shift();
}
}
return modelLevel;
};
return buildModelLevel(headings, 1);
};
fluid.tableOfContents.modelBuilder.gradualModelLevelFn = function (modelLevel, subHeadings) {
// Clone the subHeadings because we don't want to modify the reference of the subHeadings.
// the reference will affect the equality condition in generateTree(), resulting an unwanted tree.
var subHeadingsClone = fluid.copy(subHeadings);
subHeadingsClone[0].level--;
return subHeadingsClone;
};
fluid.tableOfContents.modelBuilder.skippedModelLevelFn = function (modelLevel, subHeadings) {
modelLevel.push({headings: subHeadings});
return modelLevel;
};
fluid.tableOfContents.modelBuilder.convertToHeadingObjects = function (that, headings, anchorInfo) {
headings = $(headings);
return fluid.transform(headings, function (heading, index) {
return {
level: that.headingCalculator.getHeadingLevel(heading),
text: $(heading).text(),
url: anchorInfo[index].url
};
});
};
fluid.tableOfContents.modelBuilder.assembleModel = function (that, headings, anchorInfo) {
var headingInfo = that.convertToHeadingObjects(headings, anchorInfo);
return that.toModel(headingInfo);
};
fluid.defaults("fluid.tableOfContents.modelBuilder", {
gradeNames: ["fluid.component"],
components: {
headingCalculator: {
type: "fluid.tableOfContents.modelBuilder.headingCalculator"
}
},
invokers: {
toModel: {
funcName: "fluid.tableOfContents.modelBuilder.toModel",
args: ["{arguments}.0", "{modelBuilder}.modelLevelFn"]
},
modelLevelFn: "fluid.tableOfContents.modelBuilder.gradualModelLevelFn",
convertToHeadingObjects: "fluid.tableOfContents.modelBuilder.convertToHeadingObjects({that}, {arguments}.0, {arguments}.1)", // headings, anchorInfo
assembleModel: "fluid.tableOfContents.modelBuilder.assembleModel({that}, {arguments}.0, {arguments}.1)" // headings, anchorInfo
}
});
/*************************************
* ToC ModelBuilder headingCalculator *
**************************************/
fluid.registerNamespace("fluid.tableOfContents.modelBuilder.headingCalculator");
fluid.tableOfContents.modelBuilder.headingCalculator.getHeadingLevel = function (that, heading) {
return that.options.levels.indexOf(heading.tagName) + 1;
};
fluid.defaults("fluid.tableOfContents.modelBuilder.headingCalculator", {
gradeNames: ["fluid.component"],
invokers: {
getHeadingLevel: "fluid.tableOfContents.modelBuilder.headingCalculator.getHeadingLevel({that}, {arguments}.0)" // heading
},
levels: ["H1", "H2", "H3", "H4", "H5", "H6"]
});
/*************
* ToC Levels *
**************/
fluid.registerNamespace("fluid.tableOfContents.levels");
/**
* Create an object model based on the type and ID. The object should contain an
* ID that maps the selectors (ie. level1:), and the object should contain a children
* @param string Accepted values are: level, items
* @param int The current level which is used here as the ID.
*/
fluid.tableOfContents.levels.objModel = function (type, ID) {
var objModel = {
ID: type + ID + ":",
children: []
};
return objModel;
};
/**
* Configure item object when item object has no text, uri, level in it.
* defaults to add a decorator to hide the bullets.
*/
fluid.tableOfContents.levels.handleEmptyItemObj = function (itemObj) {
itemObj.decorators = [{
type: "addClass",
classes: "fl-tableOfContents-hide-bullet"
}];
};
/**
* @param Object that.model, the model with all the headings, it should be in the format of {headings: [...]}
* @param int the current level we want to generate the tree for. default to 1 if not defined.
* @return Object A tree that looks like {children: [{ID: x, subTree:[...]}, ...]}
*/
fluid.tableOfContents.levels.generateTree = function (headingsModel, currentLevel) {
currentLevel = currentLevel || 0;
var levelObj = fluid.tableOfContents.levels.objModel("level", currentLevel);
// FLUID-4352, run generateTree if there are headings in the model.
if (headingsModel.headings.length === 0) {
return currentLevel ? [] : {children: []};
}
// base case: level is 0, returns {children:[generateTree(nextLevel)]}
// purpose is to wrap the first level with a children object.
if (currentLevel === 0) {
var tree = {
children: [
fluid.tableOfContents.levels.generateTree(headingsModel, currentLevel + 1)
]
};
return tree;
}
// Loop through the heading array, which can have multiple headings on the same level
$.each(headingsModel.headings, function (index, model) {
var itemObj = fluid.tableOfContents.levels.objModel("items", currentLevel);
var linkObj = {
ID: "link" + currentLevel,
target: model.url,
linktext: model.text
};
// If level is undefined, then add decorator to it, otherwise add the links to it.
if (!model.level) {
fluid.tableOfContents.levels.handleEmptyItemObj(itemObj);
} else {
itemObj.children.push(linkObj);
}
// If there are sub-headings, go into the next level recursively
if (model.headings) {
itemObj.children.push(fluid.tableOfContents.levels.generateTree(model, currentLevel + 1));
}
// At this point, the itemObj should be in a tree format with sub-headings children
levelObj.children.push(itemObj);
});
return levelObj;
};
/**
* @return Object Returned produceTree must be in {headings: [trees]}
*/
fluid.tableOfContents.levels.produceTree = function (that) {
var tree = fluid.tableOfContents.levels.generateTree(that.model);
// Add the header to the tree
tree.children.push({
ID: "tocHeader",
messagekey: "tocHeader"
});
return tree;
};
fluid.tableOfContents.levels.fetchResources = function (that) {
fluid.fetchResources(that.options.resources, function () {
that.container.append(that.options.resources.template.resourceText);
that.refreshView();
});
};
fluid.defaults("fluid.tableOfContents.levels", {
gradeNames: ["fluid.rendererComponent"],
produceTree: "fluid.tableOfContents.levels.produceTree",
strings: {
tocHeader: "Table of Contents"
},
selectors: {
tocHeader: ".flc-toc-header",
level1: ".flc-toc-levels-level1",
level2: ".flc-toc-levels-level2",
level3: ".flc-toc-levels-level3",
level4: ".flc-toc-levels-level4",
level5: ".flc-toc-levels-level5",
level6: ".flc-toc-levels-level6",
items1: ".flc-toc-levels-items1",
items2: ".flc-toc-levels-items2",
items3: ".flc-toc-levels-items3",
items4: ".flc-toc-levels-items4",
items5: ".flc-toc-levels-items5",
items6: ".flc-toc-levels-items6",
link1: ".flc-toc-levels-link1",
link2: ".flc-toc-levels-link2",
link3: ".flc-toc-levels-link3",
link4: ".flc-toc-levels-link4",
link5: ".flc-toc-levels-link5",
link6: ".flc-toc-levels-link6"
},
repeatingSelectors: ["level1", "level2", "level3", "level4", "level5", "level6", "items1", "items2", "items3", "items4", "items5", "items6"],
model: {
headings: [] // [text: heading, url: linkURL, headings: [ an array of subheadings in the same format]
},
listeners: {
"onCreate.fetchResources": "fluid.tableOfContents.levels.fetchResources"
},
resources: {
template: {
forceCache: true,
url: "../html/TableOfContents.html"
}
},
rendererFnOptions: {
noexpand: true
},
rendererOptions: {
debugMode: false
}
});
})(jQuery, fluid_2_0_0);

15
lib/infusion/src/components/tableOfContents/tableOfContentsDependencies.json

@ -0,0 +1,15 @@
{
"tableOfContents": {
"name": "Table of Contents",
"description": "Present an automatic table of contents from any HTML page.",
"files": [
"./js/TableOfContents.js"
],
"dependencies": [
"jQuery",
"normalize",
"framework",
"renderer"
]
}
}

113
lib/infusion/src/components/textToSpeech/js/MockTTS.js

@ -0,0 +1,113 @@
/*
Copyright 2015 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
/* global fluid */
(function () {
"use strict";
// Mocks the fluid.textToSpeech component, removing calls to the
// Web Speech API. This will allow for tests to run in browsers
// that don't support the Web Speech API.
fluid.defaults("fluid.mock.textToSpeech", {
gradeNames: ["fluid.textToSpeech"],
members: {
// An archive of all the calls to queueSpeech.
// Will contain an ordered set of objects -- {text: String, options: Object}.
speechRecord: [],
// An archive of all the events fired
// Will contain a key/value pairing where key is the name of the event and the
// value is the number of times the event was fired.
eventRecord: {}
},
listeners: {
"onStart.recordEvent": {
listener: "{that}.recordEvent",
args: ["onStart"]
},
"onStop.recordEvent": {
listener: "{that}.recordEvent",
args: ["onStop"]
},
"onSpeechQueued.recordEvent": {
listener: "{that}.recordEvent",
args: ["onSpeechQueued"]
}
},
invokers: {
queueSpeech: {
funcName: "fluid.mock.textToSpeech.queueSpeech",
args: ["{that}", "{that}.handleStart", "{that}.handleEnd", "{that}.speechRecord", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
},
cancel: {
funcName: "fluid.mock.textToSpeech.cancel",
args: ["{that}", "{that}.handleEnd"]
},
pause: {
"this": null, // TODO: This needs to be removed once FLUID-5714 is fixed
method: null,
func: "{that}.events.onPause.fire"
},
resume: {
"this": null,
method: null,
func: "{that}.events.onResume.fire"
},
getVoices: {
"this": null,
method: null,
funcName: "fluid.identity",
args: []
},
recordEvent: {
funcName: "fluid.mock.textToSpeech.recordEvent",
args: ["{that}.eventRecord", "{arguments}.0"]
}
}
});
fluid.mock.textToSpeech.queueSpeech = function (that, handleStart, handleEnd, speechRecord, text, interrupt, options) {
if (interrupt) {
that.cancel();
}
var record = {
text: text,
interrupt: !!interrupt
};
if (options) {
record.options = options;
}
speechRecord.push(record);
that.queue.push(text);
that.events.onSpeechQueued.fire(text);
// mocking speechSynthesis speak
handleStart();
// using setTimeout to preserve asynchronous behaviour
setTimeout(handleEnd, 0);
};
fluid.mock.textToSpeech.cancel = function (that, handleEnd) {
that.queue = [];
handleEnd();
};
fluid.mock.textToSpeech.recordEvent = function (eventRecord, name) {
eventRecord[name] = (eventRecord[name] || 0) + 1;
};
})();

288
lib/infusion/src/components/textToSpeech/js/TextToSpeech.js

@ -0,0 +1,288 @@
/*
Copyright 2015-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
Includes code from Underscore.js 1.8.3
http://underscorejs.org
(c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
Underscore may be freely distributed under the MIT license.
*/
/* global speechSynthesis, SpeechSynthesisUtterance*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.textToSpeech");
/******************************************************************************************* *
* fluid.textToSpeech provides a wrapper around the SpeechSynthesis Interface *
* from the Web Speech API ( https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html ) *
*********************************************************************************************/
fluid.textToSpeech.isSupported = function () {
return !!(window && window.speechSynthesis);
};
/**
* Ensures that TTS is supported in the browser, including cases where the
* feature is detected, but where the underlying audio engine is missing.
* For example in VMs on SauceLabs, the behaviour for browsers which report that the speechSynthesis
* API is implemented is for the `onstart` event of an utterance to never fire. If we don't receive this
* event within a timeout, this API's behaviour is to return a promise which rejects.
*
* @param delay {Number} A time in milliseconds to wait for the speechSynthesis to fire its onStart event
* by default it is 5000ms (5s). This is crux of the test, as it needs time to attempt to run the speechSynthesis.
* @return {fluid.promise} A promise which will resolve if the TTS is supported (the onstart event is fired within the delay period)
* or be rejected otherwise.
*/
fluid.textToSpeech.checkTTSSupport = function (delay) {
var promise = fluid.promise();
if (fluid.textToSpeech.isSupported()) {
// MS Edge speech synthesizer won't speak if the text string is blank,
// so this must contain actual text
var toSpeak = new SpeechSynthesisUtterance("short"); // short text to attempt to speak
toSpeak.volume = 0; // mutes the Speech Synthesizer
// Same timeout as the timeout in the IoC testing framework
var timeout = setTimeout(function () {
fluid.textToSpeech.deferredSpeechSynthesisControl("cancel");
promise.reject();
}, delay || 5000);
toSpeak.onend = function () {
clearTimeout(timeout);
fluid.textToSpeech.deferredSpeechSynthesisControl("cancel");
promise.resolve();
};
fluid.textToSpeech.deferredSpeechSynthesisControl("speak", toSpeak);
} else {
setTimeout(promise.reject, 0);
}
return promise;
};
fluid.defaults("fluid.textToSpeech", {
gradeNames: ["fluid.modelComponent"],
events: {
onStart: null,
onStop: null,
onError: null,
onSpeechQueued: null
},
members: {
queue: []
},
// Model paths: speaking, pending, paused, utteranceOpts, pauseRequested, resumeRequested
model: {
// Changes to the utteranceOpts will only text that is queued after the change.
// All of these options can be overriden in the queueSpeech method by passing in
// options directly there. It is useful in cases where a single instance needs to be
// spoken with different options (e.g. single text in a different language.)
utteranceOpts: {
// text: "", // text to synthesize. avoid as it will override any other text passed in
// lang: "", // the language of the synthesized text
// voice: {} // a WebSpeechSynthesis object; if not set, will use the default one provided by the browser
// volume: 1, // a value between 0 and 1
// rate: 1, // a value from 0.1 to 10 although different synthesizers may have a smaller range
// pitch: 1, // a value from 0 to 2
}
},
modelListeners: {
"speaking": {
listener: "fluid.textToSpeech.speak",
args: ["{that}", "{change}.value"]
},
"pauseRequested": {
listener: "fluid.textToSpeech.requestControl",
args: ["{that}", "pause", "{change}"]
},
"resumeRequested": {
listener: "fluid.textToSpeech.requestControl",
args: ["{that}", "resume", "{change}"]
}
},
invokers: {
queueSpeech: {
funcName: "fluid.textToSpeech.queueSpeech",
args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
},
cancel: {
funcName: "fluid.textToSpeech.cancel",
args: ["{that}"]
},
pause: {
changePath: "pauseRequested",
value: true
},
resume: {
changePath: "resumeRequested",
value: true
},
getVoices: {
"this": "speechSynthesis",
"method": "getVoices"
},
handleStart: {
changePath: "speaking",
value: true
},
// The handleEnd method is assumed to be triggered asynchronously
// as it is processed/triggered by the mechanism voicing the utterance.
handleEnd: {
funcName: "fluid.textToSpeech.handleEnd",
args: ["{that}"]
},
handleError: "{that}.events.onError.fire",
handlePause: {
changePath: "paused",
value: true
},
handleResume: {
changePath: "paused",
value: false
}
}
});
// Issue commands to the speechSynthesis interface with deferral (1 ms timeout);
// this makes the wrapper behave better when issuing commands, especially
// play and pause
fluid.textToSpeech.deferredSpeechSynthesisControl = function (control, args) {
setTimeout(function () {
speechSynthesis[control](args);
}, 1);
};
// Throttle implementation adapted from underscore.js 1.8.3; see
// file header for license details
// Returns a version of a function that will only be called max once
// every "wait" MS
fluid.textToSpeech.throttle = function (func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) {
options = {};
}
var later = function () {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
};
var throttled = function () {
var now = new Date().getTime();
if (!previous && options.leading === false) {
previous = now;
}
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
// Throttled version of deferred speech synthesis control
fluid.textToSpeech.throttleControl = fluid.textToSpeech.throttle(fluid.textToSpeech.deferredSpeechSynthesisControl, 100, {leading: false});
fluid.textToSpeech.speak = function (that, speaking) {
that.events[speaking ? "onStart" : "onStop"].fire();
};
fluid.textToSpeech.requestControl = function (that, control, change) {
// If there's a control request (value change to true), clear and
// execute it
if (change.value) {
that.applier.change(change.path, false);
fluid.textToSpeech.throttleControl(control);
}
};
fluid.textToSpeech.handleEnd = function (that) {
that.queue.shift();
var resetValues = {
speaking: false,
pending: false,
paused: false
};
if (that.queue.length) {
that.applier.change("pending", true);
} else if (!that.queue.length) {
var newModel = $.extend({}, that.model, resetValues);
that.applier.change("", newModel);
}
};
fluid.textToSpeech.queueSpeech = function (that, text, interrupt, options) {
if (interrupt) {
that.cancel();
}
var errorFn = function () {
that.handleError(text);
};
var toSpeak = new SpeechSynthesisUtterance(text);
var eventBinding = {
onstart: that.handleStart,
onend: that.handleEnd,
onerror: errorFn,
onpause: that.handlePause,
onresume: that.handleResume
};
$.extend(toSpeak, that.model.utteranceOpts, options, eventBinding);
// Store toSpeak additionally on the queue to help deal
// with premature garbage collection described at https://bugs.chromium.org/p/chromium/issues/detail?id=509488#c11
// this makes the speech synthesis behave much better in Safari in
// particular
that.queue.push({text: text, utterance: toSpeak});
that.events.onSpeechQueued.fire(text);
fluid.textToSpeech.deferredSpeechSynthesisControl("speak", toSpeak);
};
fluid.textToSpeech.cancel = function (that) {
that.queue = [];
fluid.textToSpeech.deferredSpeechSynthesisControl("cancel");
};
})(jQuery, fluid_2_0_0);

14
lib/infusion/src/components/textToSpeech/textToSpeechDependencies.json

@ -0,0 +1,14 @@
{
"textToSpeech": {
"name": "Text To Speech",
"description": "A wrapper around the Web Speech API for Speech Synthesis",
"cssFiles": [],
"files": [
"./js/TextToSpeech.js"
],
"dependencies": [
"jQuery",
"framework"
]
}
}

312
lib/infusion/src/components/textfieldSlider/js/TextfieldSlider.js

@ -0,0 +1,312 @@
/*
Copyright 2013-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/********************
* Textfield Slider *
********************/
fluid.defaults("fluid.textfieldSlider", {
gradeNames: ["fluid.viewComponent", "fluid.contextAware"],
components: {
textfield: {
type: "fluid.textfieldSlider.textfield",
container: "{textfieldSlider}.dom.textfield",
options: {
model: "{textfieldSlider}.model",
range: "{textfieldSlider}.options.range",
ariaOptions: "{textfieldSlider}.options.ariaOptions",
strings: "{textfieldSlider}.options.strings"
}
},
slider: {
container: "{textfieldSlider}.dom.slider",
options: {
model: "{textfieldSlider}.model",
range: "{textfieldSlider}.options.range",
sliderOptions: "{textfieldSlider}.options.sliderOptions",
ariaOptions: "{textfieldSlider}.options.ariaOptions",
strings: "{textfieldSlider}.options.strings"
}
}
},
contextAwareness: {
sliderVariety: {
checks: {
jQueryUI: {
contextValue: "{fluid.prefsWidgetType}",
equals: "jQueryUI",
gradeNames: "fluid.textfieldSlider.jQueryUI"
}
},
defaultGradeNames: "fluid.textfieldSlider.nativeHTML"
}
},
selectors: {
textfield: ".flc-textfieldSlider-field",
slider: ".flc-textfieldSlider-slider"
},
model: {
value: null
},
modelRelay: {
target: "value",
singleTransform: {
type: "fluid.transforms.limitRange",
input: "{that}.model.value",
min: "{that}.options.range.min",
max: "{that}.options.range.max"
}
},
range: {
min: 0,
max: 100
},
ariaOptions: {
// Specified by implementor
// ID of an external label to refer to with aria-labelledby
// attribute
// "aria-labelledby": ""
},
sliderOptions: {
orientation: "horizontal",
step: 1.0
},
strings: {
// Specified by implementor
// text of label to apply to both textfield and slider input
// via aria-label attribute
// "aria-label": ""
}
});
fluid.defaults("fluid.textfieldSlider.nativeHTML", {
components: {
slider: {
type: "fluid.slider.native"
}
}
});
fluid.defaults("fluid.textfieldSlider.jQueryUI", {
components: {
slider: {
type: "fluid.slider.jQuery"
}
}
});
fluid.defaults("fluid.textfieldSlider.textfield", {
gradeNames: ["fluid.viewComponent"],
range: {}, // should be used to specify the min, max range e.g. {min: 0, max: 100}
modelRelay: {
target: "value",
singleTransform: {
type: "fluid.transforms.stringToNumber",
input: "{that}.model.stringValue"
}
},
modelListeners: {
value: {
"this": "{that}.container",
"method": "val",
args: ["{change}.value"]
},
// TODO: This listener is to deal with the issue that, when the input field receives a invalid input such as a string value,
// ignore it and populate the field with the previous value.
// This is an area in which UX has spilled over into our model configuration, which to some extent we should try to prevent.
// Whenever we receive a "change" event or some other similar checkpoint, if these updates occurred any faster, the user would
// be infuriated by being unable to type into the field. This situation doesn't occur at the moment because the change event is
// only fired when users leave the input feild. At the very least, we need to give a namespace to this listener - unfortunately
// the current dataBinding implementation will ignore it. Having this listener here represents an interaction decision rather
// than an implementation decision. This issue needs to be revisited.
stringValue: {
"this": "{that}.container",
"method": "val",
args: ["{that}.model.value"]
}
},
listeners: {
"onCreate.bindChangeEvt": {
"this": "{that}.container",
"method": "change",
"args": ["{that}.setModel"]
},
"onCreate.initTextfieldAttributes": {
"this": "{that}.container",
method: "attr",
args: [{
"aria-labelledby": "{that}.options.ariaOptions.aria-labelledby",
"aria-label": "{that}.options.strings.aria-label"
}]
}
},
invokers: {
setModel: {
changePath: "stringValue",
value: "{arguments}.0.target.value"
}
}
});
// Base slider grade
fluid.defaults("fluid.slider", {
gradeNames: ["fluid.viewComponent"],
range: {} // should be used to specify the min, max range e.g. {min: 0, max: 100}
});
fluid.defaults("fluid.slider.native", {
gradeNames: ["fluid.slider"],
modelRelay: {
target: "value",
singleTransform: {
type: "fluid.transforms.stringToNumber",
input: "{that}.model.stringValue"
}
},
invokers: {
setModel: {
changePath: "stringValue",
value: {
expander: {
"this": "{that}.container",
"method": "val"
}
}
}
},
listeners: {
"onCreate.initSliderAttributes": {
"this": "{that}.container",
method: "attr",
args: [{
"min": "{that}.options.range.min",
"max": "{that}.options.range.max",
"step": "{that}.options.sliderOptions.step",
"type": "range",
"value": "{that}.model.value",
"aria-labelledby": "{that}.options.ariaOptions.aria-labelledby",
"aria-label": "{that}.options.strings.aria-label"
}]
},
"onCreate.bindSlideEvt": {
"this": "{that}.container",
"method": "on",
"args": ["input", "{that}.setModel"]
},
"onCreate.bindRangeChangeEvt": {
"this": "{that}.container",
"method": "on",
"args": ["change", "{that}.setModel"]
}
},
modelListeners: {
"value": [{
"this": "{that}.container",
"method": "val",
args: ["{change}.value"],
// If we don't exclude init, the value can get
// set before onCreate.initSliderAttributes
// sets min / max / step, which messes up the
// initial slider rendering
excludeSource: "init"
}]
}
});
fluid.defaults("fluid.slider.jQuery", {
gradeNames: ["fluid.slider"],
selectors: {
thumb: ".ui-slider-handle"
},
styles: {
handle: "fl-slider-handle",
range: "fl-slider-range"
},
members: {
slider: {
expander: {
"this": "{that}.container",
method: "slider",
args: ["{that}.combinedSliderOptions"]
}
},
combinedSliderOptions: {
expander: {
funcName: "fluid.slider.combineSliderOptions",
args: ["{that}.options.sliderOptions", "{that}.options.range"]
}
}
},
sliderOptions: {
orientation: "horizontal",
step: 1.0,
classes: {
"ui-slider-handle": "{that}.options.styles.handle",
"ui-slider-range": "{that}.options.styles.range"
}
},
invokers: {
setSliderValue: {
"this": "{that}.slider",
"method": "slider",
args: ["value", "{arguments}.0"]
},
setSliderAriaValueNow: {
"this": "{that}.dom.thumb",
"method": "attr",
args: ["aria-valuenow", "{arguments}.0"]
},
setModel: {
changePath: "value",
value: "{arguments}.1.value"
}
},
listeners: {
// This can be removed once the jQuery UI slider has built in ARIA
"onCreate.initSliderAria": {
"this": "{that}.dom.thumb",
method: "attr",
args: [{
role: "slider",
"aria-valuenow": "{that}.combinedSliderOptions.value",
"aria-valuemin": "{that}.combinedSliderOptions.min",
"aria-valuemax": "{that}.combinedSliderOptions.max",
"aria-labelledby": "{that}.options.ariaOptions.aria-labelledby",
"aria-label": "{that}.options.strings.aria-label"
}]
},
"onCreate.bindSlideEvt": {
"this": "{that}.slider",
"method": "on",
"args": ["slide", "{that}.setModel"]
}
},
modelListeners: {
"value": [{
listener: "{that}.setSliderValue",
args: ["{change}.value"]
}, {
listener: "{that}.setSliderAriaValueNow",
args: ["{change}.value"]
}]
}
});
fluid.slider.combineSliderOptions = function (sliderOptions, model, range) {
return $.extend(true, {}, sliderOptions, model, range);
};
})(jQuery, fluid_2_0_0);

18
lib/infusion/src/components/textfieldSlider/textfieldSliderDependencies.json

@ -0,0 +1,18 @@
{
"textfieldSlider": {
"name": "Textfield Slider",
"description": "A synchronized slider and textfield",
"cssFiles": [],
"files": [
"./js/TextfieldSlider.js"
],
"dependencies": [
"jQuery",
"jQueryUI",
"normalize",
"framework",
"enhancement",
"jQueryTouchPunchPlugin"
]
}
}

45
lib/infusion/src/components/uiOptions/js/UIOptions.js

@ -0,0 +1,45 @@
/*
Copyright 2013-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
// Gradename to invoke "fluid.uiOptions.prefsEditor"
fluid.prefs.builder({
gradeNames: ["fluid.prefs.auxSchema.starter"]
});
fluid.defaults("fluid.uiOptions.prefsEditor", {
gradeNames: ["fluid.prefs.constructed.prefsEditor"],
lazyLoad: false,
distributeOptions: [{
record: "{that}.options.lazyLoad",
target: "{that separatedPanel}.options.lazyLoad"
}, {
source: "{that}.options.tocTemplate",
target: "{that uiEnhancer}.options.tocTemplate"
}, {
source: "{that}.options.ignoreForToC",
target: "{that uiEnhancer}.options.ignoreForToC"
}],
enhancer: {
distributeOptions: [{
source: "{that}.options.tocTemplate",
target: "{that > fluid.prefs.enactor.tableOfContents}.options.tocTemplate"
}, {
source: "{that}.options.ignoreForToC",
target: "{that > fluid.prefs.enactor.tableOfContents}.options.ignoreForToC"
}]
}
});
})(jQuery, fluid_2_0_0);

14
lib/infusion/src/components/uiOptions/uiOptionsDependencies.json

@ -0,0 +1,14 @@
{
"uiOptions": {
"name": "User Interface Options",
"description": "An instance of a preference editor created by the Infusion Preferences Framework. Users customize their preferences and needs for viewing content.",
"files": [
"./js/UIOptions.js"
],
"dependencies": [
"jQuery",
"framework",
"preferences"
]
}
}

69
lib/infusion/src/framework/core/css/fluid.css

@ -0,0 +1,69 @@
/* focus */
.fl-focus:focus,
.fl-focus :focus {
outline: 2px solid black;
}
/* Container alignment */
.fl-force-right {float:right;}
.fl-force-left {float:left;}
.fl-centered {margin-left:auto; margin-right:auto; display:block;}
/* The following styles are based on 3rd party software */
/*
* The following styles are based on the Micro Clearfix solution:
* http://nicolasgallagher.com/micro-clearfix-hack/
*/
.fl-clearfix:before, .fl-clearfix:after {content:""; display:table;}
.fl-clearfix:after {clear:both;}
/* End of Micro Clearfix based styles */
/*
* The following styles are based on css from HTML5 Boilerplate v4.3:
* http://html5boilerplate.com
*/
/* Hide from both screenreaders and browsers */
.fl-hidden {
display: none !important;
visibility: hidden;
}
/* Hide visually and from screenreaders, but maintain layout */
.fl-hidden-invisible {
visibility: hidden;
}
/* Hide only visually, but have it available for screenreaders */
.fl-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
/*
* Extends the .visuallyhidden class to allow the element to be focusable
* when navigated to via the keyboard
*/
.fl-hidden-accessible.fl-focus:active,
.fl-hidden-accessible.fl-focus:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
/* End of HTML5 Boilerplate based styles */

132
lib/infusion/src/framework/core/css/fluidDebugging.css

@ -0,0 +1,132 @@
.fl-debug-holder {
position: fixed;
bottom: 0;
right: 0;
width: 100%;
height: 0px;
background-color: #dff;
}
.fl-debug-holder-open {
height: 20em;
}
.fl-debug-holder-closed {
height: 0px;
}
.fl-debug-open-pane-trigger {
width: 32px;
height: 32px;
position: absolute;
top: -37px;
right: 5px;
background: url("../images/debug_tab.png");
}
.fl-debug-open-pane-trigger:hover {
background-color: #bbb;
}
.fl-debug-pane {
width: 100%;
height: 100%;
font-family: OpenSans, 'Helvetica Neue', Helvetica, Helvetica, Arial, sans-serif;
font-size: 16px;
color: black;
padding-top: 40px;
overflow-y: scroll;
overflow-x: hidden;
}
.fl-debug-pane thead {
padding-bottom: 2px;
font-weight: bold;
}
.fl-debug-pointer-events-none {
pointer-events: none;
}
.fl-debug-highlightElement {
z-index: 999999;
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
}
.fl-debug-highlightRoot {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
.flc-debug-pane-indexel {
width: 2em;
height: 1em;
margin: 0.2em;
border: 2px solid black
}
.fl-debug-inspect-trigger {
width: 32px;
height: 32px;
position: absolute;
top: 5px;
left: 5px;
background: url("../images/magnifying_glass.png");
}
.fl-debug-inspect-trigger:hover {
background-color: #ddd;
}
.fl-debug-inspect-active {
background-color: #aaa;
}
.fl-debug-inspect-active:hover {
background-color: #888;
}
.fl-debug-selector-cell {
text-align: right;
padding-right: 1em;
}
/* See http://stackoverflow.com/questions/14765817/why-does-css-td-width-not-work */
.fl-debug-pane-index {
min-width: 3em;
}
.fl-debug-pane-dom-id {
min-width: 11em;
}
.fl-debug-pane-component-id {
min-width: 11em;
}
.fl-debug-pane-grades {
min-width: 40em;
}
.fl-debug-line {
min-width: 18em;
}
.fl-debug-tooltip {
width: 40em;
font-size: 10px;
position: absolute;
background-color: #FFC;
border: 2px solid #444;
padding: 0.5em;
}
.flc-debug-tooltip-trigger:hover {
background-color: rgba(128, 128, 128, 0.3);
}

26
lib/infusion/src/framework/core/frameworkDependencies.json

@ -0,0 +1,26 @@
{
"framework": {
"name": "Infusion Framework Core",
"description": "A suite of core Infusion framework features, including support for events, views, data binding, and keyboard accessibility. Required by all Fluid components.",
"files": [
"./js/Fluid.js",
"./js/FluidPromises.js",
"./js/FluidDocument.js",
"./js/FluidDOMUtilities.js",
"./js/JavaProperties.js",
"./js/FluidDebugging.js",
"./js/FluidIoC.js",
"./js/DataBinding.js",
"./js/ModelTransformation.js",
"./js/ModelTransformationTransforms.js",
"./js/jquery.keyboard-a11y.js",
"./js/FluidView.js",
"./js/FluidRequests.js",
"./js/ResourceLoader.js"
],
"dependencies": [
"jQuery",
"jQueryUI"
]
}
}

BIN
lib/infusion/src/framework/core/images/debug_tab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
lib/infusion/src/framework/core/images/magnifying_glass.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1495
lib/infusion/src/framework/core/js/DataBinding.js

File diff suppressed because it is too large Load Diff

2806
lib/infusion/src/framework/core/js/Fluid.js

File diff suppressed because it is too large Load Diff

116
lib/infusion/src/framework/core/js/FluidDOMUtilities.js

@ -0,0 +1,116 @@
/*
Copyright 2008-2010 University of Cambridge
Copyright 2008-2009 University of Toronto
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.dom = fluid.dom || {};
// Node walker function for iterateDom.
var getNextNode = function (iterator) {
if (iterator.node.firstChild) {
iterator.node = iterator.node.firstChild;
iterator.depth += 1;
return iterator;
}
while (iterator.node) {
if (iterator.node.nextSibling) {
iterator.node = iterator.node.nextSibling;
return iterator;
}
iterator.node = iterator.node.parentNode;
iterator.depth -= 1;
}
return iterator;
};
/**
* Walks the DOM, applying the specified acceptor function to each element.
* There is a special case for the acceptor, allowing for quick deletion of elements and their children.
* Return "delete" from your acceptor function if you want to delete the element in question.
* Return "stop" to terminate iteration.
* Implementation note - this utility exists mainly for performance reasons. It was last tested
* carefully some time ago (around jQuery 1.2) but at that time was around 3-4x faster at raw DOM
* filtration tasks than the jQuery equivalents, which was an important source of performance loss in the
* Reorderer component. General clients of the framework should use this method with caution if at all, and
* the performance issues should be reassessed when we have time.
*
* @param {Element} node the node to start walking from
* @param {Function} acceptor the function to invoke with each DOM element
* @param {Boolean} allnodes Use <code>true</code> to call acceptor on all nodes,
* rather than just element nodes (type 1)
*/
fluid.dom.iterateDom = function (node, acceptor, allNodes) {
var currentNode = {node: node, depth: 0};
var prevNode = node;
var condition;
while (currentNode.node !== null && currentNode.depth >= 0 && currentNode.depth < fluid.dom.iterateDom.DOM_BAIL_DEPTH) {
condition = null;
if (currentNode.node.nodeType === 1 || allNodes) {
condition = acceptor(currentNode.node, currentNode.depth);
}
if (condition) {
if (condition === "delete") {
currentNode.node.parentNode.removeChild(currentNode.node);
currentNode.node = prevNode;
}
else if (condition === "stop") {
return currentNode.node;
}
}
prevNode = currentNode.node;
currentNode = getNextNode(currentNode);
}
};
// Work around IE circular DOM issue. This is the default max DOM depth on IE.
// http://msdn2.microsoft.com/en-us/library/ms761392(VS.85).aspx
fluid.dom.iterateDom.DOM_BAIL_DEPTH = 256;
/**
* Checks if the specified container is actually the parent of containee.
*
* @param {Element} container the potential parent
* @param {Element} containee the child in question
*/
fluid.dom.isContainer = function (container, containee) {
for (; containee; containee = containee.parentNode) {
if (container === containee) {
return true;
}
}
return false;
};
/** Return the element text from the supplied DOM node as a single String.
* Implementation note - this is a special-purpose utility used in the framework in just one
* position in the Reorderer. It only performs a "shallow" traversal of the text and was intended
* as a quick and dirty means of extracting element labels where the user had not explicitly provided one.
* It should not be used by general users of the framework and its presence here needs to be
* reassessed.
*/
fluid.dom.getElementText = function (element) {
var nodes = element.childNodes;
var text = "";
for (var i = 0; i < nodes.length; ++i) {
var child = nodes[i];
if (child.nodeType === 3) {
text = text + child.nodeValue;
}
}
return text;
};
})(jQuery, fluid_2_0_0);

300
lib/infusion/src/framework/core/js/FluidDebugging.js

@ -0,0 +1,300 @@
/*
Copyright 2007-2010 University of Cambridge
Copyright 2007-2009 University of Toronto
Copyright 2007-2009 University of California, Berkeley
Copyright 2010 OCAD University
Copyright 2010-2011 Lucendo Development Ltd.
Copyright 2012 Raising the Floor - US
Copyright 2014-2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
var fluid = fluid || fluid_2_0_0;
(function ($, fluid) {
"use strict";
/** Render a timestamp from a Date object into a helpful fixed format for debug logs to millisecond accuracy
* @param date {Date} The date to be rendered
* @return {String} A string format consisting of hours:minutes:seconds.millis for the datestamp padded to fixed with
*/
fluid.renderTimestamp = function (date) {
var zeropad = function (num, width) {
if (!width) { width = 2; }
var numstr = (num === undefined ? "" : num.toString());
return "00000".substring(5 - width + numstr.length) + numstr;
};
return zeropad(date.getHours()) + ":" + zeropad(date.getMinutes()) + ":" + zeropad(date.getSeconds()) + "." + zeropad(date.getMilliseconds(), 3);
};
fluid.isTracing = false;
fluid.registerNamespace("fluid.tracing");
fluid.tracing.pathCount = [];
fluid.tracing.summarisePathCount = function (pathCount) {
pathCount = pathCount || fluid.tracing.pathCount;
var togo = {};
for (var i = 0; i < pathCount.length; ++i) {
var path = pathCount[i];
if (!togo[path]) {
togo[path] = 1;
}
else {
++togo[path];
}
}
var toReallyGo = [];
fluid.each(togo, function (el, path) {
toReallyGo.push({path: path, count: el});
});
toReallyGo.sort(function (a, b) {return b.count - a.count;});
return toReallyGo;
};
fluid.tracing.condensePathCount = function (prefixes, pathCount) {
prefixes = fluid.makeArray(prefixes);
var prefixCount = {};
fluid.each(prefixes, function (prefix) {
prefixCount[prefix] = 0;
});
var togo = [];
fluid.each(pathCount, function (el) {
var path = el.path;
if (!fluid.find(prefixes, function (prefix) {
if (path.indexOf(prefix) === 0) {
prefixCount[prefix] += el.count;
return true;
}
})) {
togo.push(el);
}
});
fluid.each(prefixCount, function (count, path) {
togo.unshift({path: path, count: count});
});
return togo;
};
// Exception stripping code taken from https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
// BSD licence, see header
fluid.detectStackStyle = function (e) {
var style = "other";
var stackStyle = {
offset: 0
};
if (e.arguments) {
style = "chrome";
} else if (typeof window !== "undefined" && window.opera && e.stacktrace) {
style = "opera10";
} else if (e.stack) {
style = "firefox";
// Detect FireFox 4-style stacks which are 1 level less deep
stackStyle.offset = e.stack.indexOf("Trace exception") === -1 ? 1 : 0;
} else if (typeof window !== "undefined" && window.opera && !("stacktrace" in e)) { //Opera 9-
style = "opera";
}
stackStyle.style = style;
return stackStyle;
};
fluid.obtainException = function () {
try {
throw new Error("Trace exception");
}
catch (e) {
return e;
}
};
var stackStyle = fluid.detectStackStyle(fluid.obtainException());
fluid.registerNamespace("fluid.exceptionDecoders");
fluid.decodeStack = function () {
if (stackStyle.style !== "firefox") {
return null;
}
var e = fluid.obtainException();
return fluid.exceptionDecoders[stackStyle.style](e);
};
fluid.exceptionDecoders.firefox = function (e) {
var delimiter = "at ";
var lines = e.stack.replace(/(?:\n@:0)?\s+$/m, "").replace(/^\(/gm, "{anonymous}(").split("\n");
return fluid.transform(lines, function (line) {
line = line.replace(/\)/g, "");
var atind = line.indexOf(delimiter);
return atind === -1 ? [line] : [line.substring(atind + delimiter.length), line.substring(0, atind)];
});
};
// Main entry point for callers.
fluid.getCallerInfo = function (atDepth) {
atDepth = (atDepth || 3) - stackStyle.offset;
var stack = fluid.decodeStack();
var element = stack && stack[atDepth][0];
if (element) {
var lastslash = element.lastIndexOf("/");
if (lastslash === -1) {
lastslash = 0;
}
var nextColon = element.indexOf(":", lastslash);
return {
path: element.substring(0, lastslash),
filename: element.substring(lastslash + 1, nextColon),
index: element.substring(nextColon + 1)
};
} else {
return null;
}
};
/** Generates a string for padding purposes by replicating a character a given number of times
* @param c {Character} A character to be used for padding
* @param count {Integer} The number of times to repeat the character
* @return A string of length <code>count</code> consisting of repetitions of the supplied character
*/
// UNOPTIMISED
fluid.generatePadding = function (c, count) {
var togo = "";
for (var i = 0; i < count; ++i) {
togo += c;
}
return togo;
};
// Marker so that we can render a custom string for properties which are not direct and concrete
fluid.SYNTHETIC_PROPERTY = {};
// utility to avoid triggering custom getter code which could throw an exception - e.g. express 3.x's request object
fluid.getSafeProperty = function (obj, key) {
var desc = Object.getOwnPropertyDescriptor(obj, key); // supported on all of our environments - is broken on IE8
return desc && !desc.get ? obj[key] : fluid.SYNTHETIC_PROPERTY;
};
function printImpl(obj, small, options) {
function out(str) {
options.output += str;
}
var big = small + options.indentChars, isFunction = typeof(obj) === "function";
if (options.maxRenderChars !== undefined && options.output.length > options.maxRenderChars) {
return true;
}
if (obj === null) {
out("null");
} else if (obj === undefined) {
out("undefined"); // NB - object invalid for JSON interchange
} else if (obj === fluid.SYNTHETIC_PROPERTY) {
out("[Synthetic property]");
} else if (fluid.isPrimitive(obj) && !isFunction) {
out(JSON.stringify(obj));
}
else {
if (options.stack.indexOf(obj) !== -1) {
out("(CIRCULAR)"); // NB - object invalid for JSON interchange
return;
}
options.stack.push(obj);
var i;
if (fluid.isArrayable(obj)) {
if (obj.length === 0) {
out("[]");
} else {
out("[\n" + big);
for (i = 0; i < obj.length; ++i) {
if (printImpl(obj[i], big, options)) {
return true;
}
if (i !== obj.length - 1) {
out(",\n" + big);
}
}
out("\n" + small + "]");
}
}
else {
out("{" + (isFunction ? " Function" : "") + "\n" + big); // NB - Function object invalid for JSON interchange
var keys = fluid.keys(obj);
for (i = 0; i < keys.length; ++i) {
var key = keys[i];
var value = fluid.getSafeProperty(obj, key);
out(JSON.stringify(key) + ": ");
if (printImpl(value, big, options)) {
return true;
}
if (i !== keys.length - 1) {
out(",\n" + big);
}
}
out("\n" + small + "}");
}
options.stack.pop();
}
return;
}
/** Render a complex JSON object into a nicely indented format suitable for human readability.
* @param obj {Object} The object to be rendered
* @param options {Object} An options structure governing the rendering process. This supports the following options:
* <code>indent</code> {Integer} the number of space characters to be used to indent each level of containment (default value: 4)
* <code>maxRenderChars</code> {Integer} rendering the object will cease once this number of characters has been generated
*/
fluid.prettyPrintJSON = function (obj, options) {
options = $.extend({indent: 4, stack: [], output: ""}, options);
options.indentChars = fluid.generatePadding(" ", options.indent);
printImpl(obj, "", options);
return options.output;
};
/**
* Dumps a DOM element into a readily recognisable form for debugging - produces a
* "semi-selector" summarising its tag name, class and id, whichever are set.
*
* @param {jQueryable} element The element to be dumped
* @return A string representing the element.
*/
fluid.dumpEl = function (element) {
var togo;
if (!element) {
return "null";
}
if (element.nodeType === 3 || element.nodeType === 8) {
return "[data: " + element.data + "]";
}
if (element.nodeType === 9) {
return "[document: location " + element.location + "]";
}
if (!element.nodeType && fluid.isArrayable(element)) {
togo = "[";
for (var i = 0; i < element.length; ++i) {
togo += fluid.dumpEl(element[i]);
if (i < element.length - 1) {
togo += ", ";
}
}
return togo + "]";
}
element = $(element);
togo = element.get(0).tagName;
if (element.id) {
togo += "#" + element.id;
}
if (element.attr("class")) {
togo += "." + element.attr("class");
}
return togo;
};
})(jQuery, fluid_2_0_0);

191
lib/infusion/src/framework/core/js/FluidDocument.js

@ -0,0 +1,191 @@
/*
Copyright 2007-2010 University of Cambridge
Copyright 2007-2009 University of Toronto
Copyright 2010-2011 Lucendo Development Ltd.
Copyright 2010-2016 OCAD University
Copyright 2013-2014 Raising the Floor - US
Copyright 2005-2013 jQuery Foundation, Inc. and other contributors
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
/** This file contains functions which depend on the presence of a DOM document
* but which do not depend on the contents of Fluid.js **/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
// polyfill for $.browser which was removed in jQuery 1.9 and later
// Taken from jquery-migrate-1.2.1.js,
// jQuery Migrate - v1.2.1 - 2013-05-08
// https://github.com/jquery/jquery-migrate
// Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors; Licensed MIT
fluid.uaMatch = function (ua) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
/(msie) ([\w.]+)/.exec( ua ) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || [];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
var matched, browser;
// Don't clobber any existing jQuery.browser in case it's different
if (!$.browser) {
if (!!navigator.userAgent.match(/Trident\/7\./)) {
browser = { // From http://stackoverflow.com/questions/18684099/jquery-fail-to-detect-ie-11
msie: true,
version: 11
};
} else {
matched = fluid.uaMatch(navigator.userAgent);
browser = {};
if (matched.browser) {
browser[matched.browser] = true;
browser.version = matched.version;
}
// Chrome is Webkit, but Webkit is also Safari.
if (browser.chrome) {
browser.webkit = true;
} else if (browser.webkit) {
browser.safari = true;
}
}
$.browser = browser;
}
// Private constants.
var NAMESPACE_KEY = "fluid-scoped-data";
/**
* Gets stored state from the jQuery instance's data map.
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.getScopedData = function (target, key) {
var data = $(target).data(NAMESPACE_KEY);
return data ? data[key] : undefined;
};
/**
* Stores state in the jQuery instance's data map. Unlike jQuery's version,
* accepts multiple-element jQueries.
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.setScopedData = function (target, key, value) {
$(target).each(function () {
var data = $.data(this, NAMESPACE_KEY) || {};
data[key] = value;
$.data(this, NAMESPACE_KEY, data);
});
};
/** Global focus manager - makes use of "focusin" event supported in jquery 1.4.2 or later.
*/
var lastFocusedElement = null;
$(document).on("focusin", function (event) {
lastFocusedElement = event.target;
});
fluid.getLastFocusedElement = function () {
return lastFocusedElement;
};
var ENABLEMENT_KEY = "enablement";
/** Queries or sets the enabled status of a control. An activatable node
* may be "disabled" in which case its keyboard bindings will be inoperable
* (but still stored) until it is reenabled again.
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.enabled = function (target, state) {
target = $(target);
if (state === undefined) {
return fluid.getScopedData(target, ENABLEMENT_KEY) !== false;
}
else {
$("*", target).add(target).each(function () {
if (fluid.getScopedData(this, ENABLEMENT_KEY) !== undefined) {
fluid.setScopedData(this, ENABLEMENT_KEY, state);
}
else if (/select|textarea|input/i.test(this.nodeName)) {
$(this).prop("disabled", !state);
}
});
fluid.setScopedData(target, ENABLEMENT_KEY, state);
}
};
fluid.initEnablement = function (target) {
fluid.setScopedData(target, ENABLEMENT_KEY, true);
};
// This utility is required through the use of newer versions of jQuery which will obscure the original
// event responsible for interaction with a target. This is currently use in Tooltip.js and FluidView.js
// "dead man's blur" but would be of general utility
fluid.resolveEventTarget = function (event) {
while (event.originalEvent && event.originalEvent.target) {
event = event.originalEvent;
}
return event.target;
};
// These function (fluid.focus() and fluid.blur()) serve several functions. They should be used by
// all implementation both in test cases and component implementation which require to trigger a focus
// event. Firstly, they restore the old behaviour in jQuery versions prior to 1.10 in which a focus
// trigger synchronously relays to a focus handler. In newer jQueries this defers to the real browser
// relay with numerous platform and timing-dependent effects.
// Secondly, they are necessary since simulation of focus events by jQuery under IE
// is not sufficiently good to intercept the "focusin" binding. Any code which triggers
// focus or blur synthetically throughout the framework and client code must use this function,
// especially if correct cross-platform interaction is required with the "deadMansBlur" function.
function applyOp(node, func) {
node = $(node);
node.trigger("fluid-" + func);
node.triggerHandler(func);
node[func]();
return node;
}
$.each(["focus", "blur"], function (i, name) {
fluid[name] = function (elem) {
return applyOp(elem, name);
};
});
/* Sets the value to the DOM element and triggers the change event on the element.
* Note: when using jQuery val() function to change the node value, the change event would
* not be fired automatically, it requires to be initiated by the user.
*
* @param node {A jQueryable DOM element} A selector, a DOM node, or a jQuery instance
* @param value {String|Number|Array} A string of text, a number, or an array of strings
* corresponding to the value of each matched element to set in the node
*/
fluid.changeElementValue = function (node, value) {
node = $(node);
node.val(value).change();
};
})(jQuery, fluid_2_0_0);

2458
lib/infusion/src/framework/core/js/FluidIoC.js

File diff suppressed because it is too large Load Diff

266
lib/infusion/src/framework/core/js/FluidPromises.js

@ -0,0 +1,266 @@
/*!
Copyright 2011 unscriptable.com / John Hann
Copyright 2014 Lucendo Development Ltd.
Copyright 2014 Raising the Floor - US
Copyright 2014-2016 Raising the Floor - International
License MIT
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
// Light fluidification of minimal promises library. See original gist at
// https://gist.github.com/unscriptable/814052 for limitations and commentary
// This implementation provides what could be described as "flat promises" with
// no support for structured programming idioms involving promise composition.
// It provides what a proponent of mainstream promises would describe as
// a "glorified callback aggregator"
fluid.promise = function () {
var that = {
onResolve: [],
onReject: []
// disposition
// value
};
that.then = function (onResolve, onReject) {
if (onResolve) {
if (that.disposition === "resolve") {
onResolve(that.value);
} else {
that.onResolve.push(onResolve);
}
}
if (onReject) {
if (that.disposition === "reject") {
onReject(that.value);
} else {
that.onReject.push(onReject);
}
}
return that;
};
that.resolve = function (value) {
if (that.disposition) {
fluid.fail("Error: resolving promise ", that,
" which has already received \"" + that.disposition + "\"");
} else {
that.complete("resolve", that.onResolve, value);
}
return that;
};
that.reject = function (reason) {
if (that.disposition) {
fluid.fail("Error: rejecting promise ", that,
"which has already received \"" + that.disposition + "\"");
} else {
that.complete("reject", that.onReject, reason);
}
return that;
};
// PRIVATE, NON-API METHOD
that.complete = function (which, queue, arg) {
that.disposition = which;
that.value = arg;
for (var i = 0; i < queue.length; ++i) {
queue[i](arg);
}
};
return that;
};
/** Any object with a member <code>then</code> of type <code>function</code> passes this test.
* This includes essentially every known variety, including jQuery promises.
*/
fluid.isPromise = function (totest) {
return totest && typeof(totest.then) === "function";
};
/** Coerces any value to a promise
* @param promiseOrValue The value to be coerced
* @return If the supplied value is already a promise, it is returned unchanged. Otherwise a fresh promise is created with the value as resolution and returned
*/
fluid.toPromise = function (promiseOrValue) {
if (fluid.isPromise(promiseOrValue)) {
return promiseOrValue;
} else {
var togo = fluid.promise();
togo.resolve(promiseOrValue);
return togo;
}
};
/** Chains the resolution methods of one promise (target) so that they follow those of another (source).
* That is, whenever source resolves, target will resolve, or when source rejects, target will reject, with the
* same payloads in each case.
*/
fluid.promise.follow = function (source, target) {
source.then(target.resolve, target.reject);
};
/** Returns a promise whose resolved value is mapped from the source promise or value by the supplied function.
* @param source {Object|Promise} An object or promise whose value is to be mapped
* @param func {Function} A function which will map the resolved promise value
* @return {Promise} A promise for the resolved mapped value.
*/
fluid.promise.map = function (source, func) {
var promise = fluid.toPromise(source);
var togo = fluid.promise();
promise.then(function (value) {
var mapped = func(value);
if (fluid.isPromise(mapped)) {
fluid.promise.follow(mapped, togo);
} else {
togo.resolve(mapped);
}
}, function (error) {
togo.reject(error);
});
return togo;
};
/* General skeleton for all sequential promise algorithms, e.g. transform, reduce, sequence, etc.
* These accept a variable "strategy" pair to customise the interchange of values and final return
*/
fluid.promise.makeSequencer = function (sources, options, strategy) {
if (!fluid.isArrayable(sources)) {
fluid.fail("fluid.promise sequence algorithms must be supplied an array as source");
}
return {
sources: sources,
resolvedSources: [], // the values of "sources" only with functions invoked (an array of promises or values)
index: 0,
strategy: strategy,
options: options, // available to be supplied to each listener
returns: [],
promise: fluid.promise() // the final return value
};
};
fluid.promise.progressSequence = function (that, retValue) {
that.returns.push(retValue);
that.index++;
// No we dun't have no tail recursion elimination
fluid.promise.resumeSequence(that);
};
fluid.promise.processSequenceReject = function (that, error) { // Allow earlier promises in the sequence to wrap the rejection supplied by later ones (FLUID-5584)
for (var i = that.index - 1; i >= 0; --i) {
var resolved = that.resolvedSources[i];
var accumulator = fluid.isPromise(resolved) && typeof(resolved.accumulateRejectionReason) === "function" ? resolved.accumulateRejectionReason : fluid.identity;
error = accumulator(error);
}
that.promise.reject(error);
};
fluid.promise.resumeSequence = function (that) {
if (that.index === that.sources.length) {
that.promise.resolve(that.strategy.resolveResult(that));
} else {
var value = that.strategy.invokeNext(that);
that.resolvedSources[that.index] = value;
if (fluid.isPromise(value)) {
value.then(function (retValue) {
fluid.promise.progressSequence(that, retValue);
}, function (error) {
fluid.promise.processSequenceReject(that, error);
});
} else {
fluid.promise.progressSequence(that, value);
}
}
};
// SEQUENCE ALGORITHM APPLYING PROMISES
fluid.promise.makeSequenceStrategy = function () {
return {
invokeNext: function (that) {
var source = that.sources[that.index];
return typeof(source) === "function" ? source(that.options) : source;
},
resolveResult: function (that) {
return that.returns;
}
};
};
// accepts an array of values, promises or functions returning promises - in the case of functions returning promises,
// will assure that at most one of these is "in flight" at a time - that is, the succeeding function will not be invoked
// until the promise at the preceding position has resolved
fluid.promise.sequence = function (sources, options) {
var sequencer = fluid.promise.makeSequencer(sources, options, fluid.promise.makeSequenceStrategy());
fluid.promise.resumeSequence(sequencer);
return sequencer.promise;
};
// TRANSFORM ALGORITHM APPLYING PROMISES
fluid.promise.makeTransformerStrategy = function () {
return {
invokeNext: function (that) {
var lisrec = that.sources[that.index];
lisrec.listener = fluid.event.resolveListener(lisrec.listener);
var value = lisrec.listener(that.returns[that.index], that.options);
return value;
},
resolveResult: function (that) {
return that.returns[that.index];
}
};
};
// Construct a "mini-object" managing the process of a sequence of transforms,
// each of which may be synchronous or return a promise
fluid.promise.makeTransformer = function (listeners, payload, options) {
listeners.unshift({listener:
function () {
return payload;
}
});
var sequencer = fluid.promise.makeSequencer(listeners, options, fluid.promise.makeTransformerStrategy());
sequencer.returns.push(null); // first dummy return from initial entry
fluid.promise.resumeSequence(sequencer);
return sequencer;
};
fluid.promise.filterNamespaces = function (listeners, namespaces) {
if (!namespaces) {
return listeners;
}
return fluid.remove_if(fluid.makeArray(listeners), function (element) {
return element.namespace && !element.softNamespace && !fluid.contains(namespaces, element.namespace);
});
};
/** Top-level API to operate a Fluid event which manages a sequence of
* chained transforms. Rather than being a standard listener accepting the
* same payload, each listener to the event accepts the payload returned by the
* previous listener, and returns either a transformed payload or else a promise
* yielding such a payload.
* @param event {fluid.eventFirer} A Fluid event to which the listeners are to be interpreted as
* elements cooperating in a chained transform. Each listener will receive arguments <code>(payload, options)</code> where <code>payload</code>
* is the (successful, resolved) return value of the previous listener, and <code>options</code> is the final argument to this function
* @param payload {Object|Promise} The initial payload input to the transform chain
* @param options {Object} A free object containing options governing the transform. Fields interpreted at this top level are:
* reverse {Boolean}: <code>true</code> if the listeners are to be called in reverse order of priority (typically the case for an inverse transform)
* filterTransforms {Array}: An array of listener namespaces. If this field is set, only the transform elements whose listener namespaces listed in this array will be applied.
* @return {fluid.promise} A promise which will yield either the final transformed value, or the response of the first transform which fails.
*/
fluid.promise.fireTransformEvent = function (event, payload, options) {
options = options || {};
var listeners = options.reverse ? fluid.makeArray(event.sortedListeners).reverse() :
fluid.makeArray(event.sortedListeners);
listeners = fluid.promise.filterNamespaces(listeners, options.filterNamespaces);
var transformer = fluid.promise.makeTransformer(listeners, payload, options);
return transformer.promise;
};
})(jQuery, fluid_2_0_0);

439
lib/infusion/src/framework/core/js/FluidRequests.js

@ -0,0 +1,439 @@
/*
Copyright 2010-2011 OCAD University
Copyright 2010-2011 Lucendo Development Ltd.
Copyright 2013 Raising the Floor - US
Copyright 2014-2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/** NOTE: All contents of this file are DEPRECATED and no entry point should be considered a supported API **/
fluid.explodeLocalisedName = function (fileName, locale, defaultLocale) {
var lastDot = fileName.lastIndexOf(".");
if (lastDot === -1 || lastDot === 0) {
lastDot = fileName.length;
}
var baseName = fileName.substring(0, lastDot);
var extension = fileName.substring(lastDot);
var segs = locale.split("_");
var exploded = fluid.transform(segs, function (seg, index) {
var shortSegs = segs.slice(0, index + 1);
return baseName + "_" + shortSegs.join("_") + extension;
});
if (defaultLocale) {
exploded.unshift(baseName + "_" + defaultLocale + extension);
}
return exploded;
};
/** Framework-global caching state for fluid.fetchResources **/
var resourceCache = {};
var pendingClass = {};
/** Accepts a hash of structures with free keys, where each entry has either
* href/url or nodeId set - on completion, callback will be called with the populated
* structure with fetched resource text in the field "resourceText" for each
* entry. Each structure may contain "options" holding raw options to be forwarded
* to jQuery.ajax().
*/
fluid.fetchResources = function (resourceSpecs, callback, options) {
var that = {
options: fluid.copy(options || {})
};
that.resourceSpecs = resourceSpecs;
that.callback = callback;
that.operate = function () {
fluid.fetchResources.fetchResourcesImpl(that);
};
fluid.each(resourceSpecs, function (resourceSpec, key) {
resourceSpec.recurseFirer = fluid.makeEventFirer({name: "I/O completion for resource \"" + key + "\""});
resourceSpec.recurseFirer.addListener(that.operate);
if (resourceSpec.url && !resourceSpec.href) {
resourceSpec.href = resourceSpec.url;
}
if (that.options.defaultLocale) {
resourceSpec.defaultLocale = that.options.defaultLocale;
if (resourceSpec.locale === undefined) {
resourceSpec.locale = that.options.defaultLocale;
}
}
});
if (that.options.amalgamateClasses) {
fluid.fetchResources.amalgamateClasses(resourceSpecs, that.options.amalgamateClasses, that.operate);
}
fluid.fetchResources.explodeForLocales(resourceSpecs);
that.operate();
return that;
};
fluid.fetchResources.explodeForLocales = function (resourceSpecs) {
fluid.each(resourceSpecs, function (resourceSpec, key) {
if (resourceSpec.locale) {
var exploded = fluid.explodeLocalisedName(resourceSpec.href, resourceSpec.locale, resourceSpec.defaultLocale);
for (var i = 0; i < exploded.length; ++i) {
var newKey = key + "$localised-" + i;
var newRecord = $.extend(true, {}, resourceSpec, {
href: exploded[i],
localeExploded: true
});
resourceSpecs[newKey] = newRecord;
}
resourceSpec.localeExploded = exploded.length;
}
});
return resourceSpecs;
};
fluid.fetchResources.condenseOneResource = function (resourceSpecs, resourceSpec, key, localeCount) {
var localeSpecs = [resourceSpec];
for (var i = 0; i < localeCount; ++i) {
var localKey = key + "$localised-" + i;
localeSpecs.unshift(resourceSpecs[localKey]);
delete resourceSpecs[localKey];
}
var lastNonError = fluid.find_if(localeSpecs, function (spec) {
return !spec.fetchError;
});
if (lastNonError) {
resourceSpecs[key] = lastNonError;
}
};
fluid.fetchResources.condenseForLocales = function (resourceSpecs) {
fluid.each(resourceSpecs, function (resourceSpec, key) {
if (typeof(resourceSpec.localeExploded) === "number") {
fluid.fetchResources.condenseOneResource(resourceSpecs, resourceSpec, key, resourceSpec.localeExploded);
}
});
};
fluid.fetchResources.notifyResources = function (that, resourceSpecs, callback) {
fluid.fetchResources.condenseForLocales(resourceSpecs);
callback(resourceSpecs);
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
// Add "synthetic" elements of *this* resourceSpec list corresponding to any
// still pending elements matching the PROLEPTICK CLASS SPECIFICATION supplied
fluid.fetchResources.amalgamateClasses = function (specs, classes, operator) {
fluid.each(classes, function (clazz) {
var pending = pendingClass[clazz];
fluid.each(pending, function (pendingrec, canon) {
specs[clazz + "!" + canon] = pendingrec;
pendingrec.recurseFirer.addListener(operator);
});
});
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.fetchResources.timeSuccessCallback = function (resourceSpec) {
if (resourceSpec.timeSuccess && resourceSpec.options && resourceSpec.options.success) {
var success = resourceSpec.options.success;
resourceSpec.options.success = function () {
var startTime = new Date();
var ret = success.apply(null, arguments);
fluid.log("External callback for URL " + resourceSpec.href + " completed - callback time: " +
(new Date().getTime() - startTime.getTime()) + "ms");
return ret;
};
}
};
// TODO: Integrate punch-through from old Engage implementation
function canonUrl(url) {
return url;
}
fluid.fetchResources.clearResourceCache = function (url) {
if (url) {
delete resourceCache[canonUrl(url)];
}
else {
fluid.clear(resourceCache);
}
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.fetchResources.handleCachedRequest = function (resourceSpec, response, fetchError) {
var canon = canonUrl(resourceSpec.href);
var cached = resourceCache[canon];
if (cached.$$firer$$) {
fluid.log("Handling request for " + canon + " from cache");
var fetchClass = resourceSpec.fetchClass;
if (fetchClass && pendingClass[fetchClass]) {
fluid.log("Clearing pendingClass entry for class " + fetchClass);
delete pendingClass[fetchClass][canon];
}
var result = {response: response, fetchError: fetchError};
resourceCache[canon] = result;
cached.fire(response, fetchError);
}
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.fetchResources.completeRequest = function (thisSpec) {
thisSpec.queued = false;
thisSpec.completeTime = new Date();
fluid.log("Request to URL " + thisSpec.href + " completed - total elapsed time: " +
(thisSpec.completeTime.getTime() - thisSpec.initTime.getTime()) + "ms");
thisSpec.recurseFirer.fire();
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.fetchResources.makeResourceCallback = function (thisSpec) {
return {
success: function (response) {
thisSpec.resourceText = response;
thisSpec.resourceKey = thisSpec.href;
if (thisSpec.forceCache) {
fluid.fetchResources.handleCachedRequest(thisSpec, response);
}
fluid.fetchResources.completeRequest(thisSpec);
},
error: function (response, textStatus, errorThrown) {
thisSpec.fetchError = {
status: response.status,
textStatus: response.textStatus,
errorThrown: errorThrown
};
if (thisSpec.forceCache) {
fluid.fetchResources.handleCachedRequest(thisSpec, null, thisSpec.fetchError);
}
fluid.fetchResources.completeRequest(thisSpec);
}
};
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.fetchResources.issueCachedRequest = function (resourceSpec, options) {
var canon = canonUrl(resourceSpec.href);
var cached = resourceCache[canon];
if (!cached) {
fluid.log("First request for cached resource with url " + canon);
cached = fluid.makeEventFirer({name: "cache notifier for resource URL " + canon});
cached.$$firer$$ = true;
resourceCache[canon] = cached;
var fetchClass = resourceSpec.fetchClass;
if (fetchClass) {
if (!pendingClass[fetchClass]) {
pendingClass[fetchClass] = {};
}
pendingClass[fetchClass][canon] = resourceSpec;
}
options.cache = false; // TODO: Getting weird "not modified" issues on Firefox
$.ajax(options);
}
else {
if (!cached.$$firer$$) {
if (cached.response) {
options.success(cached.response);
} else {
options.error(cached.fetchError);
}
}
else {
fluid.log("Request for cached resource which is in flight: url " + canon);
cached.addListener(function (response, fetchError) {
if (response) {
options.success(response);
} else {
options.error(fetchError);
}
});
}
}
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
// Compose callbacks in such a way that the 2nd, marked "external" will be applied
// first if it exists, but in all cases, the first, marked internal, will be
// CALLED WITHOUT FAIL
fluid.fetchResources.composeCallbacks = function (internal, external) {
return external ? (internal ?
function () {
try {
external.apply(null, arguments);
}
catch (e) {
fluid.log("Exception applying external fetchResources callback: " + e);
}
internal.apply(null, arguments); // call the internal callback without fail
} : external ) : internal;
};
// unsupported, NON-API function
fluid.fetchResources.composePolicy = function (target, source) {
return fluid.fetchResources.composeCallbacks(target, source);
};
fluid.defaults("fluid.fetchResources.issueRequest", {
mergePolicy: {
success: fluid.fetchResources.composePolicy,
error: fluid.fetchResources.composePolicy,
url: "reverse"
}
});
// unsupported, NON-API function
fluid.fetchResources.issueRequest = function (resourceSpec, key) {
var thisCallback = fluid.fetchResources.makeResourceCallback(resourceSpec);
var options = {
url: resourceSpec.href,
success: thisCallback.success,
error: thisCallback.error,
dataType: resourceSpec.dataType || "text"
};
fluid.fetchResources.timeSuccessCallback(resourceSpec);
options = fluid.merge(fluid.defaults("fluid.fetchResources.issueRequest").mergePolicy,
options, resourceSpec.options);
resourceSpec.queued = true;
resourceSpec.initTime = new Date();
fluid.log("Request with key " + key + " queued for " + resourceSpec.href);
if (resourceSpec.forceCache) {
fluid.fetchResources.issueCachedRequest(resourceSpec, options);
}
else {
$.ajax(options);
}
};
fluid.fetchResources.fetchResourcesImpl = function (that) {
var complete = true;
var allSync = true;
var resourceSpecs = that.resourceSpecs;
for (var key in resourceSpecs) {
var resourceSpec = resourceSpecs[key];
if (!resourceSpec.options || resourceSpec.options.async) {
allSync = false;
}
if (resourceSpec.href && !resourceSpec.completeTime) {
if (!resourceSpec.queued) {
fluid.fetchResources.issueRequest(resourceSpec, key);
}
if (resourceSpec.queued) {
complete = false;
}
}
else if (resourceSpec.nodeId && !resourceSpec.resourceText) {
var node = document.getElementById(resourceSpec.nodeId);
// upgrade this to somehow detect whether node is "armoured" somehow
// with comment or CDATA wrapping
resourceSpec.resourceText = fluid.dom.getElementText(node);
resourceSpec.resourceKey = resourceSpec.nodeId;
}
}
if (complete && that.callback && !that.callbackCalled) {
that.callbackCalled = true;
if ($.browser.mozilla && !allSync) {
// Defer this callback to avoid debugging problems on Firefox
setTimeout(function () {
fluid.fetchResources.notifyResources(that, resourceSpecs, that.callback);
}, 1);
}
else {
fluid.fetchResources.notifyResources(that, resourceSpecs, that.callback);
}
}
};
// TODO: This framework function is a stop-gap before the "ginger world" is capable of
// asynchronous instantiation. It currently performs very poor fidelity expansion of a
// component's options to discover "resources" only held in the static environment
fluid.fetchResources.primeCacheFromResources = function (componentName) {
var resources = fluid.defaults(componentName).resources;
var expanded = (fluid.expandOptions ? fluid.expandOptions : fluid.identity)(fluid.copy(resources));
fluid.fetchResources(expanded);
};
/** Utilities invoking requests for expansion **/
fluid.registerNamespace("fluid.expander");
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.expander.makeDefaultFetchOptions = function (successdisposer, failid, options) {
return $.extend(true, {dataType: "text"}, options, {
success: function (response, environmentdisposer) {
var json = JSON.parse(response);
environmentdisposer(successdisposer(json));
},
error: function (response, textStatus) {
fluid.log("Error fetching " + failid + ": " + textStatus);
}
});
};
/*
* This function is unsupported: It is not really intended for use by implementors.
*/
fluid.expander.makeFetchExpander = function (options) {
return { expander: {
type: "fluid.expander.deferredFetcher",
href: options.url,
options: fluid.expander.makeDefaultFetchOptions(options.disposer, options.url, options.options),
resourceSpecCollector: "{resourceSpecCollector}",
fetchKey: options.fetchKey
}};
};
fluid.expander.deferredFetcher = function (deliverer, source, expandOptions) {
var expander = source.expander;
var spec = fluid.copy(expander);
// fetch the "global" collector specified in the external environment to receive
// this resourceSpec
var collector = fluid.expand(expander.resourceSpecCollector, expandOptions);
delete spec.type;
delete spec.resourceSpecCollector;
delete spec.fetchKey;
var environmentdisposer = function (disposed) {
deliverer(disposed);
};
// replace the callback which is there (taking 2 arguments) with one which
// directly responds to the request, passing in the result and OUR "disposer" -
// which once the user has processed the response (say, parsing JSON and repackaging)
// finally deposits it in the place of the expander in the tree to which this reference
// has been stored at the point this expander was evaluated.
spec.options.success = function (response) {
expander.options.success(response, environmentdisposer);
};
var key = expander.fetchKey || fluid.allocateGuid();
collector[key] = spec;
return fluid.NO_VALUE;
};
})(jQuery, fluid_2_0_0);

693
lib/infusion/src/framework/core/js/FluidView.js

@ -0,0 +1,693 @@
/*
Copyright 2010-2011 Lucendo Development Ltd.
Copyright 2010-2016 OCAD University
Copyright 2012-2014 Raising the Floor - US
Copyright 2014-2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
/** This file contains functions which depend on the presence of a DOM document
* and which depend on the contents of Fluid.js **/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.defaults("fluid.viewComponent", {
gradeNames: ["fluid.modelComponent"],
initFunction: "fluid.initView",
argumentMap: {
container: 0,
options: 1
},
members: { // Used to allow early access to DOM binder via IoC, but to also avoid triggering evaluation of selectors
dom: "@expand:fluid.initDomBinder({that}, {that}.options.selectors)"
}
});
// unsupported, NON-API function
fluid.dumpSelector = function (selectable) {
return typeof (selectable) === "string" ? selectable :
selectable.selector ? selectable.selector : "";
};
// unsupported, NON-API function
// NOTE: this function represents a temporary strategy until we have more integrated IoC debugging.
// It preserves the 1.3 and previous framework behaviour for the 1.x releases, but provides a more informative
// diagnostic - in fact, it is perfectly acceptable for a component's creator to return no value and
// the failure is really in assumptions in fluid.initLittleComponent. Revisit this issue for 2.0
fluid.diagnoseFailedView = function (componentName, that, options, args) {
if (!that && fluid.hasGrade(options, "fluid.viewComponent")) {
var container = fluid.wrap(args[1]);
var message1 = "Instantiation of view component with type " + componentName + " failed, since ";
if (!container) {
fluid.fail(message1 + " container argument is empty");
}
else if (container.length === 0) {
fluid.fail(message1 + "selector \"", fluid.dumpSelector(args[1]), "\" did not match any markup in the document");
} else {
fluid.fail(message1 + " component creator function did not return a value");
}
}
};
fluid.checkTryCatchParameter = function () {
var location = window.location || { search: "", protocol: "file:" };
var GETparams = location.search.slice(1).split("&");
return fluid.find(GETparams, function (param) {
if (param.indexOf("notrycatch") === 0) {
return true;
}
}) === true;
};
fluid.notrycatch = fluid.checkTryCatchParameter();
/**
* Wraps an object in a jQuery if it isn't already one. This function is useful since
* it ensures to wrap a null or otherwise falsy argument to itself, rather than the
* often unhelpful jQuery default of returning the overall document node.
*
* @param {Object} obj the object to wrap in a jQuery
* @param {jQuery} userJQuery the jQuery object to use for the wrapping, optional - use the current jQuery if absent
*/
fluid.wrap = function (obj, userJQuery) {
userJQuery = userJQuery || $;
return ((!obj || obj.jquery) ? obj : userJQuery(obj));
};
/**
* If obj is a jQuery, this function will return the first DOM element within it. Otherwise, the object will be returned unchanged.
*
* @param {jQuery} obj the jQuery instance to unwrap into a pure DOM element
*/
fluid.unwrap = function (obj) {
return obj && obj.jquery ? obj[0] : obj;
};
/**
* Fetches a single container element and returns it as a jQuery.
*
* @param {String||jQuery||element} containerSpec an id string, a single-element jQuery, or a DOM element specifying a unique container
* @param {Boolean} fallible <code>true</code> if an empty container is to be reported as a valid condition
* @return a single-element jQuery of container
*/
fluid.container = function (containerSpec, fallible, userJQuery) {
var selector = containerSpec.selector || containerSpec;
if (userJQuery) {
containerSpec = fluid.unwrap(containerSpec);
}
var container = fluid.wrap(containerSpec, userJQuery);
if (fallible && (!container || container.length === 0)) {
return null;
}
if (!container || !container.jquery || container.length !== 1) {
if (typeof (containerSpec) !== "string") {
containerSpec = container.selector;
}
var count = container.length !== undefined ? container.length : 0;
fluid.fail((count > 1 ? "More than one (" + count + ") container elements were"
: "No container element was") + " found for selector " + containerSpec);
}
if (!fluid.isDOMNode(container[0])) {
fluid.fail("fluid.container was supplied a non-jQueryable element");
}
// To address FLUID-5966, manually adding back the selector and context properties that were removed from jQuery v3.0.
// ( see: https://jquery.com/upgrade-guide/3.0/#breaking-change-deprecated-context-and-selector-properties-removed )
// In most cases the "selector" property will already be restored through the DOM binder;
// however, when a selector or pure jQuery element is supplied directly as a component's container, we need to add them
// if it is possible to infer them. This feature is rarely used but is crucial for the prefs framework infrastructure
// in Panels.js fluid.prefs.subPanel.resetDomBinder
container.selector = selector;
container.context = container.context || containerSpec.ownerDocument || document;
return container;
};
/**
* Creates a new DOM Binder instance, used to locate elements in the DOM by name.
*
* @param {Object} container the root element in which to locate named elements
* @param {Object} selectors a collection of named jQuery selectors
*/
fluid.createDomBinder = function (container, selectors) {
// don't put on a typename to avoid confusing primitive visitComponentChildren
var that = {
id: fluid.allocateGuid(),
cache: {}
};
var userJQuery = container.constructor;
function cacheKey(name, thisContainer) {
return fluid.allocateSimpleId(thisContainer) + "-" + name;
}
function record(name, thisContainer, result) {
that.cache[cacheKey(name, thisContainer)] = result;
}
that.locate = function (name, localContainer) {
var selector, thisContainer, togo;
selector = selectors[name];
thisContainer = localContainer ? $(localContainer) : container;
if (!thisContainer) {
fluid.fail("DOM binder invoked for selector " + name + " without container");
}
if (selector === "") {
togo = thisContainer;
}
else if (!selector) {
togo = userJQuery();
}
else {
if (typeof (selector) === "function") {
togo = userJQuery(selector.call(null, fluid.unwrap(thisContainer)));
} else {
togo = userJQuery(selector, thisContainer);
}
}
if (!togo.selector) {
togo.selector = selector;
togo.context = thisContainer;
}
togo.selectorName = name;
record(name, thisContainer, togo);
return togo;
};
that.fastLocate = function (name, localContainer) {
var thisContainer = localContainer ? localContainer : container;
var key = cacheKey(name, thisContainer);
var togo = that.cache[key];
return togo ? togo : that.locate(name, localContainer);
};
that.clear = function () {
that.cache = {};
};
that.refresh = function (names, localContainer) {
var thisContainer = localContainer ? localContainer : container;
if (typeof names === "string") {
names = [names];
}
if (thisContainer.length === undefined) {
thisContainer = [thisContainer];
}
for (var i = 0; i < names.length; ++i) {
for (var j = 0; j < thisContainer.length; ++j) {
that.locate(names[i], thisContainer[j]);
}
}
};
that.resolvePathSegment = that.locate;
return that;
};
/** Expect that jQuery selector query has resulted in a non-empty set of
* results. If none are found, this function will fail with a diagnostic message,
* with the supplied message prepended.
*/
fluid.expectFilledSelector = function (result, message) {
if (result && result.length === 0 && result.jquery) {
fluid.fail(message + ": selector \"" + result.selector + "\" with name " + result.selectorName +
" returned no results in context " + fluid.dumpEl(result.context));
}
};
/**
* The central initialiation method called as the first act of every Fluid
* component. This function automatically merges user options with defaults,
* attaches a DOM Binder to the instance, and configures events.
*
* @param {String} componentName The unique "name" of the component, which will be used
* to fetch the default options from store. By recommendation, this should be the global
* name of the component's creator function.
* @param {jQueryable} container A specifier for the single root "container node" in the
* DOM which will house all the markup for this component.
* @param {Object} userOptions The configuration options for this component.
*/
// 4th argument is NOT SUPPORTED, see comments for initLittleComponent
fluid.initView = function (componentName, containerSpec, userOptions, localOptions) {
var container = fluid.container(containerSpec, true);
fluid.expectFilledSelector(container, "Error instantiating component with name \"" + componentName);
if (!container) {
return null;
}
// Need to ensure container is set early, without relying on an IoC mechanism - rethink this with asynchrony
var receiver = function (that) {
that.container = container;
};
var that = fluid.initLittleComponent(componentName, userOptions, localOptions || {gradeNames: ["fluid.viewComponent"]}, receiver);
if (!that.dom) {
fluid.initDomBinder(that);
}
// TODO: cannot afford a mutable container - put this into proper workflow
var userJQuery = that.options.jQuery; // Do it a second time to correct for jQuery injection
// if (userJQuery) {
// container = fluid.container(containerSpec, true, userJQuery);
// }
fluid.log("Constructing view component " + componentName + " with container " + container.constructor.expando +
(userJQuery ? " user jQuery " + userJQuery.expando : "") + " env: " + $.expando);
return that;
};
/**
* Creates a new DOM Binder instance for the specified component and mixes it in.
*
* @param {Object} that the component instance to attach the new DOM Binder to
*/
fluid.initDomBinder = function (that, selectors) {
if (!that.container) {
fluid.fail("fluid.initDomBinder called for component with typeName " + that.typeName +
" without an initialised container - this has probably resulted from placing \"fluid.viewComponent\" in incorrect position in grade merging order. " +
" Make sure to place it to the right of any non-view grades in the gradeNames list to ensure that it overrides properly: resolved gradeNames is ", that.options.gradeNames, " for component ", that);
}
that.dom = fluid.createDomBinder(that.container, selectors || that.options.selectors || {});
that.locate = that.dom.locate;
return that.dom;
};
// DOM Utilities.
/**
* Finds the nearest ancestor of the element that matches a predicate
* @param {Element} element DOM element
* @param {Function} test A function (predicate) accepting a DOM element, returning a truthy value representing a match
* @return The first element parent for which the predicate returns truthy - or undefined if no parent matches
*/
fluid.findAncestor = function (element, test) {
element = fluid.unwrap(element);
while (element) {
if (test(element)) {
return element;
}
element = element.parentNode;
}
};
fluid.findForm = function (node) {
return fluid.findAncestor(node, function (element) {
return element.nodeName.toLowerCase() === "form";
});
};
/** A utility with the same signature as jQuery.text and jQuery.html, but without the API irregularity
* that treats a single argument of undefined as different to no arguments */
// in jQuery 1.7.1, jQuery pulled the same dumb trick with $.text() that they did with $.val() previously,
// see comment in fluid.value below
fluid.each(["text", "html"], function (method) {
fluid[method] = function (node, newValue) {
node = $(node);
return newValue === undefined ? node[method]() : node[method](newValue);
};
});
/** A generalisation of jQuery.val to correctly handle the case of acquiring and
* setting the value of clustered radio button/checkbox sets, potentially, given
* a node corresponding to just one element.
*/
fluid.value = function (nodeIn, newValue) {
var node = fluid.unwrap(nodeIn);
var multiple = false;
if (node.nodeType === undefined && node.length > 1) {
node = node[0];
multiple = true;
}
if ("input" !== node.nodeName.toLowerCase() || !/radio|checkbox/.test(node.type)) {
// resist changes to contract of jQuery.val() in jQuery 1.5.1 (see FLUID-4113)
return newValue === undefined ? $(node).val() : $(node).val(newValue);
}
var name = node.name;
if (name === undefined) {
fluid.fail("Cannot acquire value from node " + fluid.dumpEl(node) + " which does not have name attribute set");
}
var elements;
if (multiple) {
elements = nodeIn;
} else {
elements = node.ownerDocument.getElementsByName(name);
var scope = fluid.findForm(node);
elements = $.grep(elements, function (element) {
if (element.name !== name) {
return false;
}
return !scope || fluid.dom.isContainer(scope, element);
});
}
if (newValue !== undefined) {
if (typeof(newValue) === "boolean") {
newValue = (newValue ? "true" : "false");
}
// jQuery gets this partially right, but when dealing with radio button array will
// set all of their values to "newValue" rather than setting the checked property
// of the corresponding control.
$.each(elements, function () {
this.checked = (newValue instanceof Array ?
newValue.indexOf(this.value) !== -1 : newValue === this.value);
});
} else { // this part jQuery will not do - extracting value from <input> array
var checked = $.map(elements, function (element) {
return element.checked ? element.value : null;
});
return node.type === "radio" ? checked[0] : checked;
}
};
fluid.BINDING_ROOT_KEY = "fluid-binding-root";
/** Recursively find any data stored under a given name from a node upwards
* in its DOM hierarchy **/
fluid.findData = function (elem, name) {
while (elem) {
var data = $.data(elem, name);
if (data) {
return data;
}
elem = elem.parentNode;
}
};
fluid.bindFossils = function (node, data, fossils) {
$.data(node, fluid.BINDING_ROOT_KEY, {data: data, fossils: fossils});
};
fluid.boundPathForNode = function (node, fossils) {
node = fluid.unwrap(node);
var key = node.name || node.id;
var record = fossils[key];
return record ? record.EL : null;
};
/** "Automatically" apply to whatever part of the data model is
* relevant, the changed value received at the given DOM node*/
fluid.applyBoundChange = function (node, newValue, applier) {
node = fluid.unwrap(node);
if (newValue === undefined) {
newValue = fluid.value(node);
}
if (node.nodeType === undefined && node.length > 0) {
node = node[0];
} // assume here that they share name and parent
var root = fluid.findData(node, fluid.BINDING_ROOT_KEY);
if (!root) {
fluid.fail("Bound data could not be discovered in any node above " + fluid.dumpEl(node));
}
var name = node.name;
var fossil = root.fossils[name];
if (!fossil) {
fluid.fail("No fossil discovered for name " + name + " in fossil record above " + fluid.dumpEl(node));
}
if (typeof(fossil.oldvalue) === "boolean") { // deal with the case of an "isolated checkbox"
newValue = newValue[0] ? true : false;
}
var EL = root.fossils[name].EL;
if (applier) {
applier.fireChangeRequest({path: EL, value: newValue, source: "DOM:" + node.id});
} else {
fluid.set(root.data, EL, newValue);
}
};
/**
* Returns a jQuery object given the id of a DOM node. In the case the element
* is not found, will return an empty list.
*/
fluid.jById = function (id, dokkument) {
dokkument = dokkument && dokkument.nodeType === 9 ? dokkument : document;
var element = fluid.byId(id, dokkument);
var togo = element ? $(element) : [];
togo.selector = "#" + id;
togo.context = dokkument;
return togo;
};
/**
* Returns an DOM element quickly, given an id
*
* @param {Object} id the id of the DOM node to find
* @param {Document} dokkument the document in which it is to be found (if left empty, use the current document)
* @return The DOM element with this id, or null, if none exists in the document.
*/
fluid.byId = function (id, dokkument) {
dokkument = dokkument && dokkument.nodeType === 9 ? dokkument : document;
var el = dokkument.getElementById(id);
if (el) {
// Use element id property here rather than attribute, to work around FLUID-3953
if (el.id !== id) {
fluid.fail("Problem in document structure - picked up element " +
fluid.dumpEl(el) + " for id " + id +
" without this id - most likely the element has a name which conflicts with this id");
}
return el;
} else {
return null;
}
};
/**
* Returns the id attribute from a jQuery or pure DOM element.
*
* @param {jQuery||Element} element the element to return the id attribute for
*/
fluid.getId = function (element) {
return fluid.unwrap(element).id;
};
/**
* Allocate an id to the supplied element if it has none already, by a simple
* scheme resulting in ids "fluid-id-nnnn" where nnnn is an increasing integer.
*/
fluid.allocateSimpleId = function (element) {
element = fluid.unwrap(element);
if (!element || fluid.isPrimitive(element)) {
return null;
}
if (!element.id) {
var simpleId = "fluid-id-" + fluid.allocateGuid();
element.id = simpleId;
}
return element.id;
};
/**
* Returns the document to which an element belongs, or the element itself if it is already a document
*
* @param {jQuery||Element} element The element to return the document for
* @return {Document} dokkument The document in which it is to be found
*/
fluid.getDocument = function (element) {
var node = fluid.unwrap(element);
// DOCUMENT_NODE - guide to node types at https://developer.mozilla.org/en/docs/Web/API/Node/nodeType
return node.nodeType === 9 ? node : node.ownerDocument;
};
fluid.defaults("fluid.ariaLabeller", {
gradeNames: ["fluid.viewComponent"],
labelAttribute: "aria-label",
liveRegionMarkup: "<div class=\"liveRegion fl-hidden-accessible\" aria-live=\"polite\"></div>",
liveRegionId: "fluid-ariaLabeller-liveRegion",
invokers: {
generateLiveElement: {
funcName: "fluid.ariaLabeller.generateLiveElement",
args: "{that}"
},
update: {
funcName: "fluid.ariaLabeller.update",
args: ["{that}", "{arguments}.0"]
}
},
listeners: {
onCreate: {
func: "{that}.update",
args: [null]
}
}
});
fluid.ariaLabeller.update = function (that, newOptions) {
newOptions = newOptions || that.options;
that.container.attr(that.options.labelAttribute, newOptions.text);
if (newOptions.dynamicLabel) {
var live = fluid.jById(that.options.liveRegionId);
if (live.length === 0) {
live = that.generateLiveElement();
}
live.text(newOptions.text);
}
};
fluid.ariaLabeller.generateLiveElement = function (that) {
var liveEl = $(that.options.liveRegionMarkup);
liveEl.prop("id", that.options.liveRegionId);
$("body").append(liveEl);
return liveEl;
};
var LABEL_KEY = "aria-labelling";
fluid.getAriaLabeller = function (element) {
element = $(element);
var that = fluid.getScopedData(element, LABEL_KEY);
return that;
};
/** Manages an ARIA-mediated label attached to a given DOM element. An
* aria-labelledby attribute and target node is fabricated in the document
* if they do not exist already, and a "little component" is returned exposing a method
* "update" that allows the text to be updated. */
fluid.updateAriaLabel = function (element, text, options) {
options = $.extend({}, options || {}, {text: text});
var that = fluid.getAriaLabeller(element);
if (!that) {
that = fluid.ariaLabeller(element, options);
fluid.setScopedData(element, LABEL_KEY, that);
} else {
that.update(options);
}
return that;
};
/** "Global Dismissal Handler" for the entire page. Attaches a click handler to the
* document root that will cause dismissal of any elements (typically dialogs) which
* have registered themselves. Dismissal through this route will automatically clean up
* the record - however, the dismisser themselves must take care to deregister in the case
* dismissal is triggered through the dialog interface itself. This component can also be
* automatically configured by fluid.deadMansBlur by means of the "cancelByDefault" option */
var dismissList = {};
$(document).click(function (event) {
var target = fluid.resolveEventTarget(event);
while (target) {
if (dismissList[target.id]) {
return;
}
target = target.parentNode;
}
fluid.each(dismissList, function (dismissFunc, key) {
dismissFunc(event);
delete dismissList[key];
});
});
// TODO: extend a configurable equivalent of the above dealing with "focusin" events
/** Accepts a free hash of nodes and an optional "dismissal function".
* If dismissFunc is set, this "arms" the dismissal system, such that when a click
* is received OUTSIDE any of the hierarchy covered by "nodes", the dismissal function
* will be executed.
*/
fluid.globalDismissal = function (nodes, dismissFunc) {
fluid.each(nodes, function (node) {
// Don't bother to use the real id if it is from a foreign document - we will never receive events
// from it directly in any case - and foreign documents may be under the control of malign fiends
// such as tinyMCE who allocate the same id to everything
var id = fluid.unwrap(node).ownerDocument === document ? fluid.allocateSimpleId(node) : fluid.allocateGuid();
if (dismissFunc) {
dismissList[id] = dismissFunc;
}
else {
delete dismissList[id];
}
});
};
/** Provides an abstraction for determing the current time.
* This is to provide a fix for FLUID-4762, where IE6 - IE8
* do not support Date.now().
*/
fluid.now = function () {
return Date.now ? Date.now() : (new Date()).getTime();
};
/** Sets an interation on a target control, which morally manages a "blur" for
* a possibly composite region.
* A timed blur listener is set on the control, which waits for a short period of
* time (options.delay, defaults to 150ms) to discover whether the reason for the
* blur interaction is that either a focus or click is being serviced on a nominated
* set of "exclusions" (options.exclusions, a free hash of elements or jQueries).
* If no such event is received within the window, options.handler will be called
* with the argument "control", to service whatever interaction is required of the
* blur.
*/
fluid.deadMansBlur = function (control, options) {
// TODO: This should be rewritten as a proper component
var that = {options: $.extend(true, {}, fluid.defaults("fluid.deadMansBlur"), options)};
that.blurPending = false;
that.lastCancel = 0;
that.canceller = function (event) {
fluid.log("Cancellation through " + event.type + " on " + fluid.dumpEl(event.target));
that.lastCancel = fluid.now();
that.blurPending = false;
};
that.noteProceeded = function () {
fluid.globalDismissal(that.options.exclusions);
};
that.reArm = function () {
fluid.globalDismissal(that.options.exclusions, that.proceed);
};
that.addExclusion = function (exclusions) {
fluid.globalDismissal(exclusions, that.proceed);
};
that.proceed = function (event) {
fluid.log("Direct proceed through " + event.type + " on " + fluid.dumpEl(event.target));
that.blurPending = false;
that.options.handler(control);
};
fluid.each(that.options.exclusions, function (exclusion) {
exclusion = $(exclusion);
fluid.each(exclusion, function (excludeEl) {
$(excludeEl).on("focusin", that.canceller).
on("fluid-focus", that.canceller).
click(that.canceller).mousedown(that.canceller);
// Mousedown is added for FLUID-4212, as a result of Chrome bug 6759, 14204
});
});
if (!that.options.cancelByDefault) {
$(control).on("focusout", function (event) {
fluid.log("Starting blur timer for element " + fluid.dumpEl(event.target));
var now = fluid.now();
fluid.log("back delay: " + (now - that.lastCancel));
if (now - that.lastCancel > that.options.backDelay) {
that.blurPending = true;
}
setTimeout(function () {
if (that.blurPending) {
that.options.handler(control);
}
}, that.options.delay);
});
}
else {
that.reArm();
}
return that;
};
fluid.defaults("fluid.deadMansBlur", {
gradeNames: "fluid.function",
delay: 150,
backDelay: 100
});
})(jQuery, fluid_2_0_0);

663
lib/infusion/src/framework/core/js/FluidViewDebugging.js

@ -0,0 +1,663 @@
/*
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.debug");
fluid.debug.toggleClass = function (styles, element, openStyle, closedStyle, state) {
if (openStyle) {
element.toggleClass(styles[openStyle], state);
}
if (closedStyle) {
element.toggleClass(styles[closedStyle], !state);
}
};
fluid.debug.bindToggleClick = function (element, applier, path) {
element.click(function () {
var state = fluid.get(applier.holder.model, path);
applier.change(path, !state);
});
};
fluid.defaults("fluid.debug.highlighter", {
gradeNames: ["fluid.viewComponent"],
selectors: {
highlightRoot: "#fluid-debug-highlightRoot"
},
markup: {
highlightRoot: "<div id=\"fluid-debug-highlightRoot\" class=\"fluid-debug-highlightRoot\"></div>",
highlightElement: "<div class=\"fl-debug-highlightElement\"></div>"
},
events: {
highlightClick: null
},
listeners: {
onCreate: "fluid.debug.highlighter.renderRoot"
},
invokers: {
clear: "fluid.debug.highlighter.clear({that}.dom.highlightRoot)",
highlight: "fluid.debug.highlighter.highlight({that}, {that}.dom.highlightRoot, {arguments}.0)" // dispositions
}
});
fluid.debug.highlighter.renderRoot = function (that) {
var highlightRoot = $(that.options.markup.highlightRoot);
that.container.append(highlightRoot);
highlightRoot.click(that.events.highlightClick.fire);
};
fluid.debug.highlighter.clear = function (highlightRoot) {
highlightRoot.empty();
};
fluid.debug.highlighter.positionProps = ["width","height","marginLeft","marginTop","paddingLeft","paddingTop"];
fluid.debug.highlighter.colours = {
components: [
[0, 0, 0], // black
[255, 0, 0], // red
[255, 255, 0] // yellow
],
domBinder: [0, 255, 0], // green
renderer: [0, 255, 255] // cyan
};
fluid.debug.arrayToRGBA = function (array) {
return "rgba(" + array.join(", ") + ")";
};
fluid.debug.assignColour = function (colour, alpha) {
return [colour[0], colour[1], colour[2], alpha];
};
fluid.debug.highlighter.indexToColour = function (i, isDomBind, isRenderer) {
var a = fluid.debug.assignColour, c = fluid.debug.highlighter.colours.components;
var base;
if (isRenderer) {
base = a(fluid.debug.highlighter.colours.renderer, 0.5);
} else if (isDomBind) {
base = a(fluid.debug.highlighter.colours.domBinder, 0.5);
} else {
base = a(c[i % c.length], i > c.length ? 0.2 : 0.5);
}
return base;
};
fluid.debug.isRendererSelector = function (component, selectorName) {
var isRendererComponent = fluid.componentHasGrade(component, "fluid.rendererComponent");
var ignoreContains = fluid.contains(component.options.selectorsToIgnore, selectorName);
return isRendererComponent ? (!selectorName || ignoreContains ? false : true) : false;
};
fluid.debug.highlighter.disposeEntries = function (entries, domIds) {
return fluid.transform(entries, function (entry, i) {
var component = entry.component;
var container = component.container;
var element = fluid.jById(domIds[i], container[0].ownerDocument);
var selectorName = entry.selectorName;
var isRendererSelector = fluid.debug.isRendererSelector(component, selectorName);
var noHighlight = container.is("body");
return {
component: component,
container: element,
noHighlight: noHighlight,
selectorName: selectorName,
colour: fluid.debug.highlighter.indexToColour(i, selectorName, isRendererSelector)
};
});
};
fluid.debug.domIdtoHighlightId = function (domId) {
return "highlight-for:" + domId;
};
fluid.debug.highlighter.construct = function (markup, highlightRoot, container) {
var highlight = $(markup);
highlight.prop("id", fluid.debug.domIdtoHighlightId(container.prop("id")));
highlightRoot.append(highlight);
return highlight;
};
fluid.debug.highlighter.position = function (highlight, disp, container) {
var p = fluid.debug.highlighter.positionProps;
for (var j = 0; j < p.length; ++j) {
highlight.css(p[j], container.css(p[j] || ""));
}
var offset = container.offset();
var containerBody = container[0].ownerDocument.body;
if (containerBody !== document.body) { // TODO: This primitive algorithm will not account for nested iframes
offset.left -= $(containerBody).scrollLeft();
offset.top -= $(containerBody).scrollTop();
}
highlight.offset(offset);
};
fluid.debug.highlighter.highlight = function (that, highlightRoot, dispositions) {
for (var i = 0; i < dispositions.length; ++i) {
var disp = dispositions[i];
if (disp.noHighlight) {
continue;
}
var container = disp.container;
var highlight = fluid.debug.highlighter.construct(that.options.markup.highlightElement, highlightRoot, container);
highlight.css("background-color", fluid.debug.arrayToRGBA(disp.colour));
fluid.debug.highlighter.position(highlight, disp, container);
}
};
fluid.debug.ignorableGrades = ["fluid.debug.listeningView", "fluid.debug.listeningPanel", "fluid.debug.listeningRenderer"];
fluid.debug.frameworkGrades = fluid.frameworkGrades;
fluid.debug.filterGrades = function (gradeNames) {
var highestFrameworkIndex = -1;
var output = [];
fluid.each(gradeNames, function (gradeName) { // TODO: remove fluid.indexOf
var findex = fluid.debug.frameworkGrades.indexOf(gradeName);
if (findex > highestFrameworkIndex) {
highestFrameworkIndex = findex;
} else if (findex === -1 && fluid.debug.ignorableGrades.indexOf(gradeName) === -1 && gradeName.indexOf("{") === -1) {
output.push(gradeName);
}
});
output.push(fluid.debug.frameworkGrades[highestFrameworkIndex]);
return output;
};
fluid.debug.renderDefaults = function (defaultsTemplate, typeName, options) {
return fluid.stringTemplate(defaultsTemplate, {
typeName: typeName,
options: JSON.stringify(options, null, 4)
});
};
fluid.debug.renderSelectorUsageRecurse = function (source, segs, options) {
if (fluid.isPrimitive(source)) {
if (typeof(source) === "string" && source.indexOf(options.findString) !== -1) {
var path = segs.slice(0, 2);
var usage = fluid.copy(fluid.get(options.fullSource, path));
fluid.set(options.target, path, usage);
}
} else if (fluid.isPlainObject(source)) {
fluid.each(source, function (value, key) {
segs.push(key);
fluid.debug.renderSelectorUsageRecurse(source[key], segs, options);
segs.pop(key);
});
}
};
fluid.debug.renderSelectorUsage = function (selectorUsageTemplate, selectorName, options) {
var target = {}, segs = [], findString = "}.dom." + selectorName;
fluid.debug.renderSelectorUsageRecurse(options, segs, {
findString: findString,
target: target,
fullSource: options
});
var markup = fluid.stringTemplate(selectorUsageTemplate, {selectorUsage: JSON.stringify(target, null, 4)});
return markup;
};
fluid.debug.renderIndexElement = function (indexElTemplate, colour) {
return fluid.stringTemplate(indexElTemplate, {colour: fluid.debug.arrayToRGBA(colour)});
};
fluid.debug.domIdtoRowId = function (domId) {
return "row-for:" + domId;
};
fluid.debug.rowForDomId = function (row, indexElTemplate, disp, rowIdToDomId) {
row.indexEl = fluid.debug.renderIndexElement(indexElTemplate, disp.colour);
row.domId = disp.container.prop("id");
row.rowId = fluid.debug.domIdtoRowId(row.domId);
rowIdToDomId[row.rowId] = row.domId;
};
fluid.debug.renderSelectorUsageRows = function (disp, markup, defaultsIdToContent) {
var tooltipTriggerId = fluid.allocateGuid();
var options = disp.component.options;
defaultsIdToContent[tooltipTriggerId] = fluid.debug.renderSelectorUsage(markup.selectorUsage, disp.selectorName, options);
var rows = [{
componentId: "",
extraTooltipClass: "flc-debug-tooltip-trigger",
extraGradesClass: "fl-debug-selector-cell",
grade: options.selectors[disp.selectorName],
line: disp.selectorName,
tooltipTriggerId: tooltipTriggerId
}];
return rows;
};
fluid.debug.renderDefaultsRows = function (oneGrade, markup, defaultsIdToContent) {
var defaults = fluid.defaultsStore[oneGrade];
var line = defaults && defaults.callerInfo ? defaults.callerInfo.filename + ":" + defaults.callerInfo.index : "";
// horrible mixture of semantic levels in this rendering function - don't we need a new renderer!
var extraTooltipClass = "";
var tooltipTriggerId = fluid.allocateGuid();
if (line) {
extraTooltipClass = "flc-debug-tooltip-trigger";
defaultsIdToContent[tooltipTriggerId] = fluid.debug.renderDefaults(markup.defaults, oneGrade, defaults.options);
}
return {
rowId: fluid.allocateGuid(),
indexEl: "",
domId: "",
componentId: "",
grade: oneGrade,
line: line,
extraGradesClass: "",
extraTooltipClass: extraTooltipClass,
tooltipTriggerId: tooltipTriggerId
};
};
fluid.debug.renderOneDisposition = function (disp, markup, defaultsIdToContent, rowIdToDomId) {
var rows;
if (disp.selectorName) {
rows = fluid.debug.renderSelectorUsageRows(disp, markup, defaultsIdToContent);
} else {
var filtered = fluid.debug.filterGrades(disp.component.options.gradeNames);
rows = fluid.transform(filtered, function (oneGrade) {
return fluid.debug.renderDefaultsRows(oneGrade, markup, defaultsIdToContent);
});
rows[0].componentId = disp.component.id;
}
fluid.debug.rowForDomId(rows[0], markup.indexElement, disp, rowIdToDomId);
return rows;
};
fluid.debug.renderInspecting = function (that, paneBody, markup, inspecting) {
if (!paneBody || !that.highlighter) { // stupid ginger world failure
return;
}
var defaultsIdToContent = {}; // driver for tooltips showing defaults source
paneBody.empty();
that.highlighter.clear();
var ids = fluid.keys(inspecting).reverse(); // TODO: more principled ordering
var entries = fluid.transform(ids, function (inspectingId) {
return that.viewMapper.domIdToEntry[inspectingId];
});
var dispositions = fluid.debug.highlighter.disposeEntries(entries, ids);
var rowIdToDomId = {};
var allRows = fluid.transform(dispositions, function (disp) {
return fluid.debug.renderOneDisposition(disp, markup, defaultsIdToContent, rowIdToDomId);
});
var flatRows = fluid.flatten(allRows);
var contents = fluid.transform(flatRows, function (row) {
return fluid.stringTemplate(markup.paneRow, row);
});
paneBody.html(contents.join(""));
that.highlighter.highlight(dispositions);
that.tooltips.applier.change("idToContent", defaultsIdToContent);
that.rowIdToDomId = rowIdToDomId;
that.dispositions = dispositions; // currently for looking up colour
var initSelection = fluid.arrayToHash(fluid.values(rowIdToDomId));
that.applier.change("highlightSelected", initSelection);
};
fluid.defaults("fluid.debug.browser", {
gradeNames: ["fluid.viewComponent"],
model: {
isOpen: false,
isInspecting: false,
isFrozen: false,
inspecting: {},
highlightSelected: {}
},
members: {
rowIdToDomId: {}
},
modelListeners: {
isOpen: {
funcName: "fluid.debug.toggleClass",
args: ["{that}.options.styles", "{that}.dom.holder", "holderOpen", "holderClosed", "{change}.value"]
},
isInspecting: [{
funcName: "fluid.debug.toggleClass",
args: ["{that}.options.styles", "{that}.dom.inspectTrigger", "inspecting", null, "{change}.value"]
}, {
funcName: "fluid.debug.browser.finishInspecting",
args: ["{that}", "{change}.value"]
}],
inspecting: {
funcName: "fluid.debug.renderInspecting",
args: ["{that}", "{that}.dom.paneBody", "{that}.options.markup", "{change}.value"]
},
"highlightSelected.*": {
funcName: "fluid.debug.renderHighlightSelection",
args: ["{that}", "{change}.value", "{change}.path"]
}
},
styles: {
holderOpen: "fl-debug-holder-open",
holderClosed: "fl-debug-holder-closed",
inspecting: "fl-debug-inspect-active"
},
markup: {
holder: "<div class=\"flc-debug-holder fl-debug-holder\"><div class=\"flc-debug-open-pane-trigger fl-debug-open-pane-trigger\"></div><div class=\"flc-debug-pane fl-debug-pane\"><div class=\"flc-debug-inspect-trigger fl-debug-inspect-trigger\"></div></div></div>",
pane: "<table><thead><tr><td class=\"fl-debug-pane-index\"></td><td class=\"fl-debug-pane-dom-id\">DOM ID</td><td class=\"fl-debug-pane-component-id\">Component ID</td><td class=\"fl-debug-pane-grades\">Grades / Selector</td><td class=\"fl-debug-pane-line\">Line / Selector name</td></tr></thead><tbody class=\"flc-debug-pane-body\"></tbody></table>",
paneRow: "<tr class=\"flc-debug-pane-row\" id=\"%rowId\"><td class=\"fl-debug-pane-index\">%indexEl</td><td class=\"flc-debug-dom-id\">%domId</td><td class=\"flc-debug-component-id\">%componentId</td><td class=\"flc-debug-pane-grades %extraGradesClass\">%grade</td><td class=\"flc-debug-pane-line %extraTooltipClass\" id=\"%tooltipTriggerId\">%line</td></tr>",
indexElement: "<div class=\"flc-debug-pane-indexel\" style=\"background-color: %colour\"></div>",
defaults: "<pre>fluid.defaults(\"%typeName\", %options);\n</pre>",
selectorUsage: "<pre>%selectorUsage</pre>"
},
selectors: {
openPaneTrigger: ".flc-debug-open-pane-trigger",
inspectTrigger: ".flc-debug-inspect-trigger",
holder: ".fl-debug-holder",
pane: ".fl-debug-pane",
paneBody: ".flc-debug-pane-body",
indexEl: ".flc-debug-pane-indexel",
row: ".flc-debug-pane-row"
},
events: {
onNewDocument: null,
onMarkupReady: null,
highlightClick: null
},
listeners: {
"onCreate.render": {
priority: "first",
funcName: "fluid.debug.browser.renderMarkup",
args: ["{that}", "{that}.options.markup.holder", "{that}.options.markup.pane"]
},
"onCreate.toggleTabClick": {
funcName: "fluid.debug.bindToggleClick",
args: ["{that}.dom.openPaneTrigger", "{that}.applier", "isOpen"]
},
"onCreate.toggleInspectClick": {
funcName: "fluid.debug.bindToggleClick",
args: ["{that}.dom.inspectTrigger", "{that}.applier", "isInspecting"]
},
"onCreate.bindHighlightSelection": {
funcName: "fluid.debug.browser.bindHighlightSelection",
args: ["{that}", "{that}.dom.pane"]
},
"onNewDocument.bindHover": {
funcName: "fluid.debug.browser.bindHover",
args: ["{that}", "{arguments}.0"]
},
"onNewDocument.bindHighlightClick": {
funcName: "fluid.debug.browser.bindHighlightClick",
args: ["{that}", "{arguments}.0"]
},
highlightClick: {
funcName: "fluid.debug.browser.highlightClick",
args: "{that}"
}
},
components: {
tooltips: {
createOnEvent: "onMarkupReady",
type: "fluid.tooltip",
container: "{browser}.dom.pane",
options: {
items: ".flc-debug-tooltip-trigger",
styles: {
tooltip: "fl-debug-tooltip"
},
position: {
my: "right center",
at: "left center"
},
duration: 0,
delay: 0
}
},
viewMapper: {
type: "fluid.debug.viewMapper",
options: {
events: {
onNewDocument: "{fluid.debug.browser}.events.onNewDocument"
}
}
},
highlighter: {
type: "fluid.debug.highlighter",
container: "{fluid.debug.browser}.container",
options: {
events: {
highlightClick: "{browser}.events.highlightClick"
}
}
}
}
});
fluid.debug.browser.finishInspecting = function (that, isInspecting) {
if (!isInspecting) {
var ation = that.applier.initiate();
ation.change("inspecting", null, "DELETE"); // TODO - reform this terrible API through FLUID-5373
ation.change("", {
"inspecting": {}
});
ation.change("isFrozen", false);
ation.commit();
}
};
// go into frozen state if we are not in it and are inspecting.
// if we are already frozen, finish inspecting (which will also finish frozen)
fluid.debug.browser.highlightClick = function (that) {
if (that.model.isFrozen) {
that.applier.change("isInspecting", false);
} else if (that.model.isInspecting) {
that.applier.change("isFrozen", true);
}
};
fluid.debug.browser.renderMarkup = function (that, holderMarkup, paneMarkup) {
that.container.append(holderMarkup);
var debugPane = that.locate("pane");
debugPane.append(paneMarkup);
that.events.onMarkupReady.fire();
};
fluid.debug.browser.domIdForElement = function (rowIdToDomId, rowSelector, element) {
var row = $(element).closest(rowSelector);
if (row.length > 0) {
var rowId = row[0].id;
return rowIdToDomId[rowId];
}
};
fluid.debug.browser.bindHighlightSelection = function (that, pane) {
pane.on("click", that.options.selectors.indexEl, function (evt) {
var domId = fluid.debug.browser.domIdForElement(that.rowIdToDomId, that.options.selectors.row, evt.target);
var path = ["highlightSelected", domId];
that.applier.change(path, !fluid.get(that.model, path));
});
};
fluid.debug.renderHighlightSelection = function (that, newState, path) {
var domId = path[1];
var disposition = fluid.find_if(that.dispositions, function (disp) {
return disp.container.prop("id") === domId;
});
if (disposition.noHighlight) {
return;
}
var outColour = fluid.copy(disposition.colour);
outColour[3] = outColour[3] * (newState ? 1.0 : 0.1);
var colourString = fluid.debug.arrayToRGBA(outColour);
var row = fluid.jById(fluid.debug.domIdtoRowId(domId));
$(that.options.selectors.indexEl, row).css("background-color", colourString);
fluid.jById(fluid.debug.domIdtoHighlightId(domId)).css("background-color", colourString);
};
fluid.debug.browser.bindHighlightClick = function (that, dokkument) {
// We have a global problem in that we can't accept pointer events on the highlight elements
// themselves since this will cause their own mouseenter/mouseleave events to self-block.
dokkument.on("mousedown", "*", function (evt) {
var target = $(evt.target);
var holderParents = target.parents(that.options.selectors.holder);
if (holderParents.length > 0) {
return;
}
if (that.model.isInspecting) {
that.events.highlightClick.fire();
return false;
}
});
};
fluid.debug.browser.bindHover = function (that, dokkument) {
var listener = function (event) {
if (!that.model.isInspecting || that.model.isFrozen) {
return;
}
var allParents = $(event.target).parents().addBack().get();
for (var i = 0; i < allParents.length; ++i) {
var id = allParents[i].id;
var entry = that.viewMapper.domIdToEntry[id];
if (entry) {
if (event.type === "mouseleave") {
that.applier.change(["inspecting", id], null, "DELETE");
} else if (event.type === "mouseenter") {
that.applier.change(["inspecting", id], true);
}
}
}
};
dokkument.on("mouseenter mouseleave", "*", listener);
};
fluid.defaults("fluid.debug.listeningView", {
listeners: {
onCreate: {
funcName: "fluid.debug.viewMapper.registerView",
args: ["{fluid.debug.viewMapper}", "{that}", "add"]
},
onDestroy: {
funcName: "fluid.debug.viewMapper.registerView",
args: ["{fluid.debug.viewMapper}", "{that}", "remove"]
}
}
});
fluid.defaults("fluid.debug.listeningPanel", {
listeners: {
onDomBind: {
funcName: "fluid.debug.viewMapper.registerView",
args: ["{fluid.debug.viewMapper}", "{that}", "rebind"]
}
}
});
fluid.defaults("fluid.debug.listeningRenderer", {
listeners: {
afterRender: {
funcName: "fluid.debug.viewMapper.registerView",
args: ["{fluid.debug.viewMapper}", "{that}", "rebind"]
}
}
});
fluid.defaults("fluid.debug.viewMapper", {
gradeNames: ["fluid.component", "fluid.resolveRoot"],
members: {
seenDocuments: {},
idToEntry: {},
domIdToEntry: {}
},
distributeOptions: [{
record: "fluid.debug.listeningView",
target: "{/ fluid.viewComponent}.options.gradeNames"
}, {
record: "fluid.debug.listeningPanel",
target: "{/ fluid.prefs.panel}.options.gradeNames"
}, {
record: "fluid.debug.listeningRenderer",
target: "{/ fluid.rendererComponent}.options.gradeNames"
}],
events: {
onNewDocument: null
},
listeners: {
onCreate: {
funcName: "fluid.debug.viewMapper.scanInit"
}
}
});
fluid.debug.viewMapper.registerComponent = function (that, component, containerId) {
var domBound = fluid.transform(component.options.selectors, function (selector, selectorName) {
return fluid.allocateSimpleId(component.locate(selectorName));
});
var entry = {
component: component,
containerId: containerId,
domBound: domBound
};
that.idToEntry[component.id] = entry;
if (containerId) {
that.domIdToEntry[containerId] = entry;
fluid.each(domBound, function (subId, selectorName) {
var subEntry = $.extend({}, entry);
subEntry.selectorName = selectorName;
that.domIdToEntry[subId] = subEntry;
});
}
};
fluid.debug.viewMapper.deregisterComponent = function (that, id) {
var entry = that.idToEntry[id];
delete that.idToEntry[id];
delete that.domIdToEntry[entry.containerId];
fluid.each(entry.domBound, function (subId) {
delete that.domIdToEntry[subId];
});
};
fluid.debug.viewMapper.registerView = function (that, component, action) {
var id = component.id;
var containerId = fluid.allocateSimpleId(component.container);
if (containerId) {
var dokkument = $(component.container[0].ownerDocument);
var dokkumentId = fluid.allocateSimpleId(dokkument);
if (!that.seenDocuments[dokkumentId]) {
that.seenDocuments[dokkumentId] = true;
that.events.onNewDocument.fire(dokkument);
}
}
if (action === "add") {
fluid.debug.viewMapper.registerComponent(that, component, containerId);
} else if (action === "remove") {
fluid.debug.viewMapper.deregisterComponent(that, id);
} else if (action === "rebind") {
fluid.debug.viewMapper.deregisterComponent(that, id);
fluid.debug.viewMapper.registerComponent(that, component, containerId);
}
};
fluid.debug.viewMapper.scanInit = function (that) {
var views = fluid.queryIoCSelector(fluid.rootComponent, "fluid.viewComponent");
for (var i = 0; i < views.length; ++i) {
fluid.debug.viewMapper.registerView(that, views[i], true);
}
};
$(document).ready(function () {
fluid.debug.browser("body");
});
})(jQuery, fluid_2_0_0);

117
lib/infusion/src/framework/core/js/JavaProperties.js

@ -0,0 +1,117 @@
/*
Copyright 2008-2010 University of Cambridge
Copyright 2008-2009 University of Toronto
Copyright 2010 Lucendo Development Ltd.
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
var unUnicode = /(\\u[\dabcdef]{4}|\\x[\dabcdef]{2})/g;
fluid.unescapeProperties = function (string) {
string = string.replace(unUnicode, function (match) {
var code = match.substring(2);
var parsed = parseInt(code, 16);
return String.fromCharCode(parsed);
});
var pos = 0;
while (true) {
var backpos = string.indexOf("\\", pos);
if (backpos === -1) {
break;
}
if (backpos === string.length - 1) {
return [string.substring(0, string.length - 1), true];
}
var replace = string.charAt(backpos + 1);
if (replace === "n") { replace = "\n"; }
if (replace === "r") { replace = "\r"; }
if (replace === "t") { replace = "\t"; }
string = string.substring(0, backpos) + replace + string.substring(backpos + 2);
pos = backpos + 1;
}
return [string, false];
};
var breakPos = /[^\\][\s:=]/;
fluid.parseJavaProperties = function (text) {
// File format described at http://java.sun.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)
var togo = {};
text = text.replace(/\r\n/g, "\n");
text = text.replace(/\r/g, "\n");
var lines = text.split("\n");
var contin, key, valueComp, valueRaw, valueEsc;
for (var i = 0; i < lines.length; ++i) {
var line = $.trim(lines[i]);
if (!line || line.charAt(0) === "#" || line.charAt(0) === "!") {
continue;
}
if (!contin) {
valueComp = "";
var breakpos = line.search(breakPos);
if (breakpos === -1) {
key = line;
valueRaw = "";
}
else {
key = $.trim(line.substring(0, breakpos + 1)); // +1 since first char is escape exclusion
valueRaw = $.trim(line.substring(breakpos + 2));
if (valueRaw.charAt(0) === ":" || valueRaw.charAt(0) === "=") {
valueRaw = $.trim(valueRaw.substring(1));
}
}
key = fluid.unescapeProperties(key)[0];
valueEsc = fluid.unescapeProperties(valueRaw);
}
else {
valueEsc = fluid.unescapeProperties(line);
}
contin = valueEsc[1];
if (!valueEsc[1]) { // this line was not a continuation line - store the value
togo[key] = valueComp + valueEsc[0];
}
else {
valueComp += valueEsc[0];
}
}
return togo;
};
/**
* Expand a message string with respect to a set of arguments, following a basic
* subset of the Java MessageFormat rules.
* http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
*
* The message string is expected to contain replacement specifications such
* as {0}, {1}, {2}, etc.
* @param messageString {String} The message key to be expanded
* @param args {String/Array of String} An array of arguments to be substituted into the message.
* @return The expanded message string.
*/
fluid.formatMessage = function (messageString, args) {
if (!args) {
return messageString;
}
if (typeof(args) === "string") {
args = [args];
}
for (var i = 0; i < args.length; ++i) {
messageString = messageString.replace("{" + i + "}", args[i]);
}
return messageString;
};
})(jQuery, fluid_2_0_0);

675
lib/infusion/src/framework/core/js/ModelTransformation.js

@ -0,0 +1,675 @@
/*
Copyright 2010 University of Toronto
Copyright 2010-2014 OCAD University
Copyright 2012-2014 Raising the Floor - US
Copyright 2014-2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
var fluid = fluid || fluid_2_0_0;
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.model.transform");
/** Grade definitions for standard transformation function hierarchy **/
fluid.defaults("fluid.transformFunction", {
gradeNames: "fluid.function"
});
// uses standard layout and workflow involving inputPath - an undefined input value
// will short-circuit the evaluation
fluid.defaults("fluid.standardInputTransformFunction", {
gradeNames: "fluid.transformFunction"
});
fluid.defaults("fluid.standardOutputTransformFunction", {
gradeNames: "fluid.transformFunction"
});
// defines a set of options "inputVariables" referring to its inputs, which are converted
// to functions that the transform may explicitly use to demand the input value
fluid.defaults("fluid.multiInputTransformFunction", {
gradeNames: "fluid.transformFunction"
});
// uses the standard layout and workflow involving inputPath and outputPath
fluid.defaults("fluid.standardTransformFunction", {
gradeNames: ["fluid.standardInputTransformFunction", "fluid.standardOutputTransformFunction"]
});
fluid.defaults("fluid.lens", {
gradeNames: "fluid.transformFunction",
invertConfiguration: null
// this function method returns "inverted configuration" rather than actually performing inversion
// TODO: harmonise with strategy used in VideoPlayer_framework.js
});
/***********************************
* Base utilities for transformers *
***********************************/
// unsupported, NON-API function
fluid.model.transform.pathToRule = function (inputPath) {
return {
transform: {
type: "fluid.transforms.value",
inputPath: inputPath
}
};
};
// unsupported, NON-API function
fluid.model.transform.literalValueToRule = function (input) {
return {
transform: {
type: "fluid.transforms.literalValue",
input: input
}
};
};
/** Accepts two fully escaped paths, either of which may be empty or null **/
fluid.model.composePaths = function (prefix, suffix) {
prefix = prefix === 0 ? "0" : prefix || "";
suffix = suffix === 0 ? "0" : suffix || "";
return !prefix ? suffix : (!suffix ? prefix : prefix + "." + suffix);
};
fluid.model.transform.accumulateInputPath = function (inputPath, transformer, paths) {
if (inputPath !== undefined) {
paths.push(fluid.model.composePaths(transformer.inputPrefix, inputPath));
}
};
fluid.model.transform.accumulateStandardInputPath = function (input, transformSpec, transformer, paths) {
fluid.model.transform.getValue(undefined, transformSpec[input], transformer);
fluid.model.transform.accumulateInputPath(transformSpec[input + "Path"], transformer, paths);
};
fluid.model.transform.accumulateMultiInputPaths = function (inputVariables, transformSpec, transformer, paths) {
fluid.each(inputVariables, function (v, k) {
fluid.model.transform.accumulateStandardInputPath(k, transformSpec, transformer, paths);
});
};
fluid.model.transform.getValue = function (inputPath, value, transformer) {
var togo;
if (inputPath !== undefined) { // NB: We may one day want to reverse the crazy jQuery-like convention that "no path means root path"
togo = fluid.get(transformer.source, fluid.model.composePaths(transformer.inputPrefix, inputPath), transformer.resolverGetConfig);
}
if (togo === undefined) {
// FLUID-5867 - actually helpful behaviour here rather than the insane original default of expecting a short-form value document
togo = fluid.isPrimitive(value) ? value :
("literalValue" in value ? value.literalValue :
(value.transform === undefined ? value : transformer.expand(value)));
}
return togo;
};
// distinguished value which indicates that a transformation rule supplied a
// non-default output path, and so the user should be prevented from making use of it
// in a compound transform definition
fluid.model.transform.NONDEFAULT_OUTPUT_PATH_RETURN = {};
fluid.model.transform.setValue = function (userOutputPath, value, transformer) {
// avoid crosslinking to input object - this might be controlled by a "nocopy" option in future
var toset = fluid.copy(value);
var outputPath = fluid.model.composePaths(transformer.outputPrefix, userOutputPath);
// TODO: custom resolver config here to create non-hash output model structure
if (toset !== undefined) {
transformer.applier.change(outputPath, toset);
}
return userOutputPath ? fluid.model.transform.NONDEFAULT_OUTPUT_PATH_RETURN : toset;
};
/* Resolves the <key> given as parameter by looking up the path <key>Path in the object
* to be transformed. If not present, it resolves the <key> by using the literal value if primitive,
* or expanding otherwise. <def> defines the default value if unableto resolve the key. If no
* default value is given undefined is returned
*/
fluid.model.transform.resolveParam = function (transformSpec, transformer, key, def) {
var val = fluid.model.transform.getValue(transformSpec[key + "Path"], transformSpec[key], transformer);
return (val !== undefined) ? val : def;
};
// Compute a "match score" between two pieces of model material, with 0 indicating a complete mismatch, and
// higher values indicating increasingly good matches
fluid.model.transform.matchValue = function (expected, actual, partialMatches) {
var stats = {changes: 0, unchanged: 0, changeMap: {}};
fluid.model.diff(expected, actual, stats);
// i) a pair with 0 matches counts for 0 in all cases
// ii) without "partial match mode" (the default), we simply count matches, with any mismatch giving 0
// iii) with "partial match mode", a "perfect score" in the top 24 bits is
// penalised for each mismatch, with a positive score of matches store in the bottom 24 bits
return stats.unchanged === 0 ? 0
: (partialMatches ? 0xffffff000000 - 0x1000000 * stats.changes + stats.unchanged :
(stats.changes ? 0 : 0xffffff000000 + stats.unchanged));
};
fluid.model.transform.invertPaths = function (transformSpec, transformer) {
// TODO: this will not behave correctly in the face of compound "input" which contains
// further transforms
var oldOutput = fluid.model.composePaths(transformer.outputPrefix, transformSpec.outputPath);
transformSpec.outputPath = fluid.model.composePaths(transformer.inputPrefix, transformSpec.inputPath);
transformSpec.inputPath = oldOutput;
return transformSpec;
};
// TODO: prefixApplier is a transform which is currently unused and untested
fluid.model.transform.prefixApplier = function (transformSpec, transformer) {
if (transformSpec.inputPrefix) {
transformer.inputPrefixOp.push(transformSpec.inputPrefix);
}
if (transformSpec.outputPrefix) {
transformer.outputPrefixOp.push(transformSpec.outputPrefix);
}
transformer.expand(transformSpec.input);
if (transformSpec.inputPrefix) {
transformer.inputPrefixOp.pop();
}
if (transformSpec.outputPrefix) {
transformer.outputPrefixOp.pop();
}
};
fluid.defaults("fluid.model.transform.prefixApplier", {
gradeNames: ["fluid.transformFunction"]
});
// unsupported, NON-API function
fluid.model.makePathStack = function (transform, prefixName) {
var stack = transform[prefixName + "Stack"] = [];
transform[prefixName] = "";
return {
push: function (prefix) {
var newPath = fluid.model.composePaths(transform[prefixName], prefix);
stack.push(transform[prefixName]);
transform[prefixName] = newPath;
},
pop: function () {
transform[prefixName] = stack.pop();
}
};
};
// unsupported, NON-API function
fluid.model.transform.doTransform = function (transformSpec, transformer, transformOpts) {
var expdef = transformOpts.defaults;
var transformFn = fluid.getGlobalValue(transformOpts.typeName);
if (typeof(transformFn) !== "function") {
fluid.fail("Transformation record specifies transformation function with name " +
transformSpec.type + " which is not a function - ", transformFn);
}
if (!fluid.hasGrade(expdef, "fluid.transformFunction")) {
// If no suitable grade is set up, assume that it is intended to be used as a standardTransformFunction
expdef = fluid.defaults("fluid.standardTransformFunction");
}
var transformArgs = [transformSpec, transformer];
if (fluid.hasGrade(expdef, "fluid.multiInputTransformFunction")) {
var inputs = {};
fluid.each(expdef.inputVariables, function (v, k) {
inputs[k] = function () {
var input = fluid.model.transform.getValue(transformSpec[k + "Path"], transformSpec[k], transformer);
// TODO: This is a mess, null might perfectly well be a possible default
// if no match, assign default if one exists (v != null)
input = (input === undefined && v !== null) ? v : input;
return input;
};
});
transformArgs.unshift(inputs);
}
if (fluid.hasGrade(expdef, "fluid.standardInputTransformFunction")) {
if (!("input" in transformSpec) && !("inputPath" in transformSpec)) {
fluid.fail("Error in transform specification. Either \"input\" or \"inputPath\" must be specified for a standardInputTransformFunction: received ", transformSpec);
}
var expanded = fluid.model.transform.getValue(transformSpec.inputPath, transformSpec.input, transformer);
transformArgs.unshift(expanded);
// if the function has no input, the result is considered undefined, and this is returned
if (expanded === undefined) {
return undefined;
}
}
var transformed = transformFn.apply(null, transformArgs);
if (fluid.hasGrade(expdef, "fluid.standardOutputTransformFunction")) {
// "doOutput" flag is currently set nowhere, but could be used in future
var outputPath = transformSpec.outputPath !== undefined ? transformSpec.outputPath : (transformOpts.doOutput ? "" : undefined);
if (outputPath !== undefined && transformed !== undefined) {
//If outputPath is given in the expander we want to:
// (1) output to the document
// (2) return undefined, to ensure that expanders higher up in the hierarchy doesn't attempt to output it again
fluid.model.transform.setValue(transformSpec.outputPath, transformed, transformer);
transformed = undefined;
}
}
return transformed;
};
// OLD PATHUTIL utilities: Rescued from old DataBinding implementation to support obsolete "schema" scheme for transforms - all of this needs to be rethought
var globalAccept = [];
fluid.registerNamespace("fluid.pathUtil");
/** Parses a path segment, following escaping rules, starting from character index i in the supplied path */
fluid.pathUtil.getPathSegment = function (path, i) {
fluid.pathUtil.getPathSegmentImpl(globalAccept, path, i);
return globalAccept[0];
};
/** Returns just the head segment of an EL path */
fluid.pathUtil.getHeadPath = function (path) {
return fluid.pathUtil.getPathSegment(path, 0);
};
/** Returns all of an EL path minus its first segment - if the path consists of just one segment, returns "" */
fluid.pathUtil.getFromHeadPath = function (path) {
var firstdot = fluid.pathUtil.getPathSegmentImpl(null, path, 0);
return firstdot === path.length ? "" : path.substring(firstdot + 1);
};
/** Determines whether a particular EL path matches a given path specification.
* The specification consists of a path with optional wildcard segments represented by "*".
* @param spec (string) The specification to be matched
* @param path (string) The path to be tested
* @param exact (boolean) Whether the path must exactly match the length of the specification in
* terms of path segments in order to count as match. If exact is falsy, short specifications will
* match all longer paths as if they were padded out with "*" segments
* @return (array of string) The path segments which matched the specification, or <code>null</code> if there was no match
*/
fluid.pathUtil.matchPath = function (spec, path, exact) {
var togo = [];
while (true) {
if (((path === "") ^ (spec === "")) && exact) {
return null;
}
// FLUID-4625 - symmetry on spec and path is actually undesirable, but this
// quickly avoids at least missed notifications - improved (but slower)
// implementation should explode composite changes
if (!spec || !path) {
break;
}
var spechead = fluid.pathUtil.getHeadPath(spec);
var pathhead = fluid.pathUtil.getHeadPath(path);
// if we fail to match on a specific component, fail.
if (spechead !== "*" && spechead !== pathhead) {
return null;
}
togo.push(pathhead);
spec = fluid.pathUtil.getFromHeadPath(spec);
path = fluid.pathUtil.getFromHeadPath(path);
}
return togo;
};
// unsupported, NON-API function
fluid.model.transform.expandWildcards = function (transformer, source) {
fluid.each(source, function (value, key) {
var q = transformer.queuedTransforms;
transformer.pathOp.push(fluid.pathUtil.escapeSegment(key.toString()));
for (var i = 0; i < q.length; ++i) {
if (fluid.pathUtil.matchPath(q[i].matchPath, transformer.path, true)) {
var esCopy = fluid.copy(q[i].transformSpec);
if (esCopy.inputPath === undefined || fluid.model.transform.hasWildcard(esCopy.inputPath)) {
esCopy.inputPath = "";
}
// TODO: allow some kind of interpolation for output path
// TODO: Also, we now require outputPath to be specified in these cases for output to be produced as well.. Is that something we want to continue with?
transformer.inputPrefixOp.push(transformer.path);
transformer.outputPrefixOp.push(transformer.path);
var transformOpts = fluid.model.transform.lookupType(esCopy.type);
var result = fluid.model.transform.doTransform(esCopy, transformer, transformOpts);
if (result !== undefined) {
fluid.model.transform.setValue(null, result, transformer);
}
transformer.outputPrefixOp.pop();
transformer.inputPrefixOp.pop();
}
}
if (!fluid.isPrimitive(value)) {
fluid.model.transform.expandWildcards(transformer, value);
}
transformer.pathOp.pop();
});
};
// unsupported, NON-API function
fluid.model.transform.hasWildcard = function (path) {
return typeof(path) === "string" && path.indexOf("*") !== -1;
};
// unsupported, NON-API function
fluid.model.transform.maybePushWildcard = function (transformSpec, transformer) {
var hw = fluid.model.transform.hasWildcard;
var matchPath;
if (hw(transformSpec.inputPath)) {
matchPath = fluid.model.composePaths(transformer.inputPrefix, transformSpec.inputPath);
}
else if (hw(transformer.outputPrefix) || hw(transformSpec.outputPath)) {
matchPath = fluid.model.composePaths(transformer.outputPrefix, transformSpec.outputPath);
}
if (matchPath) {
transformer.queuedTransforms.push({transformSpec: transformSpec, outputPrefix: transformer.outputPrefix, inputPrefix: transformer.inputPrefix, matchPath: matchPath});
return true;
}
return false;
};
fluid.model.sortByKeyLength = function (inObject) {
var keys = fluid.keys(inObject);
return keys.sort(fluid.compareStringLength(true));
};
// Three handler functions operating the (currently) three different processing modes
// unsupported, NON-API function
fluid.model.transform.handleTransformStrategy = function (transformSpec, transformer, transformOpts) {
if (fluid.model.transform.maybePushWildcard(transformSpec, transformer)) {
return;
}
else {
return fluid.model.transform.doTransform(transformSpec, transformer, transformOpts);
}
};
// unsupported, NON-API function
fluid.model.transform.handleInvertStrategy = function (transformSpec, transformer, transformOpts) {
transformSpec = fluid.copy(transformSpec);
// if we have a standardTransformFunction we can switch input and output arguments:
if (fluid.hasGrade(transformOpts.defaults, "fluid.standardTransformFunction")) {
transformSpec = fluid.model.transform.invertPaths(transformSpec, transformer);
}
var invertor = transformOpts.defaults && transformOpts.defaults.invertConfiguration;
if (invertor) {
var inverted = fluid.invokeGlobalFunction(invertor, [transformSpec, transformer]);
transformer.inverted.push(inverted);
}
};
// unsupported, NON-API function
fluid.model.transform.handleCollectStrategy = function (transformSpec, transformer, transformOpts) {
var defaults = transformOpts.defaults;
var standardInput = fluid.hasGrade(defaults, "fluid.standardInputTransformFunction");
var multiInput = fluid.hasGrade(defaults, "fluid.multiInputTransformFunction");
if (standardInput) {
fluid.model.transform.accumulateStandardInputPath("input", transformSpec, transformer, transformer.inputPaths);
}
if (multiInput) {
fluid.model.transform.accumulateMultiInputPaths(defaults.inputVariables, transformSpec, transformer, transformer.inputPaths);
}
if (!multiInput && !standardInput) {
var collector = defaults.collectInputPaths;
if (collector) {
var collected = fluid.makeArray(fluid.invokeGlobalFunction(collector, [transformSpec, transformer]));
transformer.inputPaths = transformer.inputPaths.concat(collected);
}
}
};
fluid.model.transform.lookupType = function (typeName, transformSpec) {
if (!typeName) {
fluid.fail("Transformation record is missing a type name: ", transformSpec);
}
if (typeName.indexOf(".") === -1) {
typeName = "fluid.transforms." + typeName;
}
var defaults = fluid.defaults(typeName);
return { defaults: defaults, typeName: typeName};
};
// unsupported, NON-API function
fluid.model.transform.processRule = function (rule, transformer) {
if (typeof(rule) === "string") {
rule = fluid.model.transform.pathToRule(rule);
}
// special dispensation to allow "literalValue" to escape any value
else if (rule.literalValue !== undefined) {
rule = fluid.model.transform.literalValueToRule(rule.literalValue);
}
var togo;
if (rule.transform) {
var transformSpec, transformOpts;
if (fluid.isArrayable(rule.transform)) {
// if the transform holds an array, each transformer within that is responsible for its own output
var transforms = rule.transform;
togo = undefined;
for (var i = 0; i < transforms.length; ++i) {
transformSpec = transforms[i];
transformOpts = fluid.model.transform.lookupType(transformSpec.type);
transformer.transformHandler(transformSpec, transformer, transformOpts);
}
} else {
// else we just have a normal single transform which will return 'undefined' as a flag to defeat cascading output
transformSpec = rule.transform;
transformOpts = fluid.model.transform.lookupType(transformSpec.type);
togo = transformer.transformHandler(transformSpec, transformer, transformOpts);
}
}
// if rule is an array, save path for later use in schema strategy on final applier (so output will be interpreted as array)
if (fluid.isArrayable(rule)) {
transformer.collectedFlatSchemaOpts = transformer.collectedFlatSchemaOpts || {};
transformer.collectedFlatSchemaOpts[transformer.outputPrefix] = "array";
}
fluid.each(rule, function (value, key) {
if (key !== "transform") {
transformer.outputPrefixOp.push(key);
var togo = transformer.expand(value, transformer);
// Value expanders and arrays as rules implicitly output, unless they have nothing (undefined) to output
if (togo !== undefined) {
fluid.model.transform.setValue(null, togo, transformer);
// ensure that expanders further up does not try to output this value as well.
togo = undefined;
}
transformer.outputPrefixOp.pop();
}
});
return togo;
};
// unsupported, NON-API function
// 3rd arg is disused by the framework and always defaults to fluid.model.transform.processRule
fluid.model.transform.makeStrategy = function (transformer, handleFn, transformFn) {
transformFn = transformFn || fluid.model.transform.processRule;
transformer.expand = function (rules) {
return transformFn(rules, transformer);
};
transformer.outputPrefixOp = fluid.model.makePathStack(transformer, "outputPrefix");
transformer.inputPrefixOp = fluid.model.makePathStack(transformer, "inputPrefix");
transformer.transformHandler = handleFn;
};
fluid.model.transform.invertConfiguration = function (rules) {
var transformer = {
inverted: []
};
fluid.model.transform.makeStrategy(transformer, fluid.model.transform.handleInvertStrategy);
transformer.expand(rules);
return {
transform: transformer.inverted
};
};
fluid.model.transform.collectInputPaths = function (rules) {
var transformer = {
inputPaths: []
};
fluid.model.transform.makeStrategy(transformer, fluid.model.transform.handleCollectStrategy);
transformer.expand(rules);
return transformer.inputPaths;
};
// unsupported, NON-API function
fluid.model.transform.flatSchemaStrategy = function (flatSchema, getConfig) {
var keys = fluid.model.sortByKeyLength(flatSchema);
return function (root, segment, index, segs) {
var path = getConfig.parser.compose.apply(null, segs.slice(0, index));
// TODO: clearly this implementation could be much more efficient
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
if (fluid.pathUtil.matchPath(key, path, true) !== null) {
return flatSchema[key];
}
}
};
};
// unsupported, NON-API function
fluid.model.transform.defaultSchemaValue = function (schemaValue) {
var type = fluid.isPrimitive(schemaValue) ? schemaValue : schemaValue.type;
return type === "array" ? [] : {};
};
// unsupported, NON-API function
fluid.model.transform.isomorphicSchemaStrategy = function (source, getConfig) {
return function (root, segment, index, segs) {
var existing = fluid.get(source, segs.slice(0, index), getConfig);
return fluid.isArrayable(existing) ? "array" : "object";
};
};
// unsupported, NON-API function
fluid.model.transform.decodeStrategy = function (source, options, getConfig) {
if (options.isomorphic) {
return fluid.model.transform.isomorphicSchemaStrategy(source, getConfig);
}
else if (options.flatSchema) {
return fluid.model.transform.flatSchemaStrategy(options.flatSchema, getConfig);
}
};
// unsupported, NON-API function
fluid.model.transform.schemaToCreatorStrategy = function (strategy) {
return function (root, segment, index, segs) {
if (root[segment] === undefined) {
var schemaValue = strategy(root, segment, index, segs);
root[segment] = fluid.model.transform.defaultSchemaValue(schemaValue);
return root[segment];
}
};
};
/** Transforms a model by a sequence of rules. Parameters as for fluid.model.transform,
* only with an array accepted for "rules"
*/
fluid.model.transform.sequence = function (source, rules, options) {
for (var i = 0; i < rules.length; ++i) {
source = fluid.model.transform(source, rules[i], options);
}
return source;
};
fluid.model.compareByPathLength = function (changea, changeb) {
var pdiff = changea.path.length - changeb.path.length;
return pdiff === 0 ? changea.sequence - changeb.sequence : pdiff;
};
/** Fires an accumulated set of change requests in increasing order of target pathlength
*/
fluid.model.fireSortedChanges = function (changes, applier) {
changes.sort(fluid.model.compareByPathLength);
fluid.fireChanges(applier, changes);
};
/**
* Transforms a model based on a specified expansion rules objects.
* Rules objects take the form of:
* {
* "target.path": "value.el.path" || {
* transform: {
* type: "transform.function.path",
* ...
* }
* }
* }
*
* @param {Object} source the model to transform
* @param {Object} rules a rules object containing instructions on how to transform the model
* @param {Object} options a set of rules governing the transformations. At present this may contain
* the values <code>isomorphic: true</code> indicating that the output model is to be governed by the
* same schema found in the input model, or <code>flatSchema</code> holding a flat schema object which
* consists of a hash of EL path specifications with wildcards, to the values "array"/"object" defining
* the schema to be used to construct missing trunk values.
*/
fluid.model.transformWithRules = function (source, rules, options) {
options = options || {};
var getConfig = fluid.model.escapedGetConfig;
var setConfig = fluid.model.escapedSetConfig;
var schemaStrategy = fluid.model.transform.decodeStrategy(source, options, getConfig);
var transformer = {
source: source,
target: {
// TODO: This should default to undefined to allow return of primitives, etc.
model: schemaStrategy ? fluid.model.transform.defaultSchemaValue(schemaStrategy(null, "", 0, [""])) : {}
},
resolverGetConfig: getConfig,
resolverSetConfig: setConfig,
collectedFlatSchemaOpts: undefined, // to hold options for flat schema collected during transforms
queuedChanges: [],
queuedTransforms: [] // TODO: This is used only by wildcard applier - explain its operation
};
fluid.model.transform.makeStrategy(transformer, fluid.model.transform.handleTransformStrategy);
transformer.applier = {
fireChangeRequest: function (changeRequest) {
changeRequest.sequence = transformer.queuedChanges.length;
transformer.queuedChanges.push(changeRequest);
}
};
fluid.bindRequestChange(transformer.applier);
transformer.expand(rules);
var rootSetConfig = fluid.copy(setConfig);
// Modify schemaStrategy if we collected flat schema options for the setConfig of finalApplier
if (transformer.collectedFlatSchemaOpts !== undefined) {
$.extend(transformer.collectedFlatSchemaOpts, options.flatSchema);
schemaStrategy = fluid.model.transform.flatSchemaStrategy(transformer.collectedFlatSchemaOpts, getConfig);
}
rootSetConfig.strategies = [fluid.model.defaultFetchStrategy, schemaStrategy ? fluid.model.transform.schemaToCreatorStrategy(schemaStrategy)
: fluid.model.defaultCreatorStrategy];
transformer.finalApplier = options.finalApplier || fluid.makeHolderChangeApplier(transformer.target, {resolverSetConfig: rootSetConfig});
if (transformer.queuedTransforms.length > 0) {
transformer.typeStack = [];
transformer.pathOp = fluid.model.makePathStack(transformer, "path");
fluid.model.transform.expandWildcards(transformer, source);
}
fluid.model.fireSortedChanges(transformer.queuedChanges, transformer.finalApplier);
return transformer.target.model;
};
$.extend(fluid.model.transformWithRules, fluid.model.transform);
fluid.model.transform = fluid.model.transformWithRules;
/** Utility function to produce a standard options transformation record for a single set of rules **/
fluid.transformOne = function (rules) {
return {
transformOptions: {
transformer: "fluid.model.transformWithRules",
config: rules
}
};
};
/** Utility function to produce a standard options transformation record for multiple rules to be applied in sequence **/
fluid.transformMany = function (rules) {
return {
transformOptions: {
transformer: "fluid.model.transform.sequence",
config: rules
}
};
};
})(jQuery, fluid_2_0_0);

743
lib/infusion/src/framework/core/js/ModelTransformationTransforms.js

@ -0,0 +1,743 @@
/*
Copyright 2010 University of Toronto
Copyright 2010-2015 OCAD University
Copyright 2013-2014 Raising the Floor - US
Copyright 2013-2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
var fluid = fluid || fluid_2_0_0;
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.model.transform");
fluid.registerNamespace("fluid.transforms");
/**********************************
* Standard transformer functions *
**********************************/
fluid.defaults("fluid.transforms.value", {
gradeNames: "fluid.standardTransformFunction",
invertConfiguration: "fluid.identity"
});
fluid.transforms.value = fluid.identity;
// Export the use of the "value" transform under the "identity" name for FLUID-5293
fluid.transforms.identity = fluid.transforms.value;
fluid.defaults("fluid.transforms.identity", {
gradeNames: "fluid.transforms.value"
});
// A helpful utility function to be used when a transform's inverse is the identity
fluid.transforms.invertToIdentity = function (transformSpec) {
transformSpec.type = "fluid.transforms.identity";
return transformSpec;
};
fluid.defaults("fluid.transforms.literalValue", {
gradeNames: "fluid.standardOutputTransformFunction"
});
fluid.transforms.literalValue = function (transformSpec) {
return transformSpec.input;
};
fluid.defaults("fluid.transforms.stringToNumber", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.stringToNumber.invert"
});
fluid.transforms.stringToNumber = function (value) {
var newValue = Number(value);
return isNaN(newValue) ? undefined : newValue;
};
fluid.transforms.stringToNumber.invert = function (transformSpec) {
transformSpec.type = "fluid.transforms.numberToString";
return transformSpec;
};
fluid.defaults("fluid.transforms.numberToString", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.numberToString.invert"
});
fluid.transforms.numberToString = function (value) {
return (typeof value !== "number") ? undefined : "" + value;
};
fluid.transforms.numberToString.invert = function (transformSpec) {
transformSpec.type = "fluid.transforms.stringToNumber";
return transformSpec;
};
fluid.defaults("fluid.transforms.count", {
gradeNames: "fluid.standardTransformFunction"
});
fluid.transforms.count = function (value) {
return fluid.makeArray(value).length;
};
fluid.defaults("fluid.transforms.round", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.invertToIdentity"
});
fluid.transforms.round = function (value) {
return Math.round(value);
};
fluid.defaults("fluid.transforms.delete", {
gradeNames: "fluid.transformFunction"
});
fluid.transforms["delete"] = function (transformSpec, transformer) {
var outputPath = fluid.model.composePaths(transformer.outputPrefix, transformSpec.outputPath);
transformer.applier.change(outputPath, null, "DELETE");
};
fluid.defaults("fluid.transforms.firstValue", {
gradeNames: "fluid.standardOutputTransformFunction"
});
fluid.transforms.firstValue = function (transformSpec, transformer) {
if (!transformSpec.values || !transformSpec.values.length) {
fluid.fail("firstValue transformer requires an array of values at path named \"values\", supplied", transformSpec);
}
for (var i = 0; i < transformSpec.values.length; i++) {
var value = transformSpec.values[i];
// TODO: problem here - all of these transforms will have their side-effects (setValue) even if only one is chosen
var expanded = transformer.expand(value);
if (expanded !== undefined) {
return expanded;
}
}
};
fluid.defaults("fluid.transforms.linearScale", {
gradeNames: ["fluid.multiInputTransformFunction",
"fluid.standardTransformFunction",
"fluid.lens" ],
invertConfiguration: "fluid.transforms.linearScale.invert",
inputVariables: {
factor: 1,
offset: 0
}
});
/* simple linear transformation */
fluid.transforms.linearScale = function (input, extraInputs) {
var factor = extraInputs.factor();
var offset = extraInputs.offset();
if (typeof(input) !== "number" || typeof(factor) !== "number" || typeof(offset) !== "number") {
return undefined;
}
return input * factor + offset;
};
/* TODO: This inversion doesn't work if the value and factors are given as paths in the source model */
fluid.transforms.linearScale.invert = function (transformSpec) {
// delete the factor and offset paths if present
delete transformSpec.factorPath;
delete transformSpec.offsetPath;
if (transformSpec.factor !== undefined) {
transformSpec.factor = (transformSpec.factor === 0) ? 0 : 1 / transformSpec.factor;
}
if (transformSpec.offset !== undefined) {
transformSpec.offset = -transformSpec.offset * (transformSpec.factor !== undefined ? transformSpec.factor : 1);
}
return transformSpec;
};
fluid.defaults("fluid.transforms.binaryOp", {
gradeNames: [ "fluid.multiInputTransformFunction", "fluid.standardOutputTransformFunction" ],
inputVariables: {
left: null,
right: null
}
});
fluid.transforms.binaryLookup = {
"===": function (a, b) { return fluid.model.isSameValue(a, b); },
"!==": function (a, b) { return !fluid.model.isSameValue(a, b); },
"<=": function (a, b) { return a <= b; },
"<": function (a, b) { return a < b; },
">=": function (a, b) { return a >= b; },
">": function (a, b) { return a > b; },
"+": function (a, b) { return a + b; },
"-": function (a, b) { return a - b; },
"*": function (a, b) { return a * b; },
"/": function (a, b) { return a / b; },
"%": function (a, b) { return a % b; },
"&&": function (a, b) { return a && b; },
"||": function (a, b) { return a || b; }
};
fluid.transforms.binaryOp = function (inputs, transformSpec, transformer) {
var left = inputs.left();
var right = inputs.right();
var operator = fluid.model.transform.getValue(undefined, transformSpec.operator, transformer);
var fun = fluid.transforms.binaryLookup[operator];
return (fun === undefined || left === undefined || right === undefined) ?
undefined : fun(left, right);
};
fluid.defaults("fluid.transforms.condition", {
gradeNames: [ "fluid.multiInputTransformFunction", "fluid.standardOutputTransformFunction" ],
inputVariables: {
"true": null,
"false": null,
"condition": null
}
});
fluid.transforms.condition = function (inputs) {
var condition = inputs.condition();
if (condition === null) {
return undefined;
}
return inputs[condition ? "true" : "false"]();
};
fluid.defaults("fluid.transforms.valueMapper", {
gradeNames: ["fluid.transformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.valueMapper.invert",
collectInputPaths: "fluid.transforms.valueMapper.collect"
});
/* unsupported, NON-API function
* sorts by the object's 'matchValue' property, where higher is better.
* Tiebreaking is done via the `index` property, where a lower index takes priority
*/
fluid.model.transform.compareMatches = function (speca, specb) {
var matchDiff = specb.matchValue - speca.matchValue;
return matchDiff === 0 ? speca.index - specb.index : matchDiff; // tiebreak using 'index'
};
fluid.transforms.valueMapper = function (transformSpec, transformer) {
if (!transformSpec.match) {
fluid.fail("valueMapper requires an array or hash of matches at path named \"match\", supplied ", transformSpec);
}
var value = fluid.model.transform.getValue(transformSpec.defaultInputPath, undefined, transformer);
var matchedEntry = (fluid.isArrayable(transformSpec.match)) ? // long form with array of records?
fluid.transforms.valueMapper.longFormMatch(value, transformSpec, transformer) :
transformSpec.match[value];
if (matchedEntry === undefined) { // if no matches found, default to noMatch
matchedEntry = transformSpec.noMatch;
}
if (matchedEntry === undefined) { // if there was no noMatch directive, return undefined
return;
}
var outputPath = matchedEntry.outputPath === undefined ? transformSpec.defaultOutputPath : matchedEntry.outputPath;
transformer.outputPrefixOp.push(outputPath);
var outputValue;
if (fluid.isPrimitive(matchedEntry)) {
outputValue = matchedEntry;
} else if (matchedEntry.outputUndefinedValue) { // if outputUndefinedValue is set, outputValue `undefined`
outputValue = undefined;
} else {
// get value from outputValue. If none is found set the outputValue to be that of defaultOutputValue (or undefined)
outputValue = fluid.model.transform.resolveParam(matchedEntry, transformer, "outputValue", undefined);
outputValue = (outputValue === undefined) ? transformSpec.defaultOutputValue : outputValue;
}
// output if we have a path and something to output
if (typeof(outputPath) === "string" && outputValue !== undefined) {
fluid.model.transform.setValue(undefined, outputValue, transformer, transformSpec.merge);
outputValue = undefined; // make sure we don't also return value
}
transformer.outputPrefixOp.pop();
return outputValue;
};
// unsupported, NON-API function
fluid.transforms.valueMapper.longFormMatch = function (valueFromDefaultPath, transformSpec, transformer) {
var o = transformSpec.match;
if (o.length === 0) {
fluid.fail("valueMapper supplied empty list of matches: ", transformSpec);
}
var matchPower = [];
for (var i = 0; i < o.length; ++i) {
var option = o[i];
var value = option.inputPath ?
fluid.model.transform.getValue(option.inputPath, undefined, transformer) : valueFromDefaultPath;
var matchValue = fluid.model.transform.matchValue(option.inputValue, value, option.partialMatches);
matchPower[i] = {index: i, matchValue: matchValue};
}
matchPower.sort(fluid.model.transform.compareMatches);
return matchPower[0].matchValue <= 0 ? undefined : o[matchPower[0].index];
};
fluid.transforms.valueMapper.invert = function (transformSpec, transformer) {
var match = [];
var togo = {
type: "fluid.transforms.valueMapper",
match: match
};
var isArray = fluid.isArrayable(transformSpec.match);
togo.defaultInputPath = fluid.model.composePaths(transformer.outputPrefix, transformSpec.defaultOutputPath);
togo.defaultOutputPath = fluid.model.composePaths(transformer.inputPrefix, transformSpec.defaultInputPath);
var def = fluid.firstDefined;
fluid.each(transformSpec.match, function (option, key) {
if (option.outputUndefinedValue === true) {
return; // don't attempt to invert undefined output value entries
}
var outOption = {};
var origInputValue = def(isArray ? option.inputValue : key, transformSpec.defaultInputValue);
if (origInputValue === undefined) {
fluid.fail("Failure inverting configuration for valueMapper - inputValue could not be resolved for record " + key + ": ", transformSpec);
}
outOption.outputValue = origInputValue;
outOption.inputValue = !isArray && fluid.isPrimitive(option) ?
option : def(option.outputValue, transformSpec.defaultOutputValue);
if (option.outputPath) {
outOption.inputPath = fluid.model.composePaths(transformer.outputPrefix, def(option.outputPath, transformSpec.outputPath));
}
if (option.inputPath) {
outOption.outputPath = fluid.model.composePaths(transformer.inputPrefix, def(option.inputPath, transformSpec.inputPath));
}
match.push(outOption);
});
return togo;
};
fluid.transforms.valueMapper.collect = function (transformSpec, transformer) {
var togo = [];
fluid.model.transform.accumulateInputPath(transformSpec.defaultInputPath, transformer, togo);
fluid.each(transformSpec.match, function (option) {
fluid.model.transform.accumulateInputPath(option.inputPath, transformer, togo);
});
return togo;
};
/* -------- arrayToSetMembership and setMembershipToArray ---------------- */
fluid.defaults("fluid.transforms.arrayToSetMembership", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.arrayToSetMembership.invert"
});
fluid.transforms.arrayToSetMembership = function (value, transformSpec, transformer) {
var output = {};
var options = transformSpec.options;
if (!value || !fluid.isArrayable(value)) {
fluid.fail("arrayToSetMembership didn't find array at inputPath nor passed as value.", transformSpec);
}
if (!options) {
fluid.fail("arrayToSetMembership requires an options block set");
}
if (transformSpec.presentValue === undefined) {
transformSpec.presentValue = true;
}
if (transformSpec.missingValue === undefined) {
transformSpec.missingValue = false;
}
fluid.each(options, function (outPath, key) {
// write to output object the value <presentValue> or <missingValue> depending on whether key is found in user input
var outVal = (value.indexOf(key) !== -1) ? transformSpec.presentValue : transformSpec.missingValue;
fluid.set(output, outPath, outVal, transformer.resolverSetConfig);
});
return output;
};
/**
* NON-API function; Copies the entire transformSpec with the following modifications:
* * A new type is set (from argument)
* * each [key]=value entry in the options is swapped to be: [value]=key
*/
fluid.transforms.arrayToSetMembership.invertWithType = function (transformSpec, transformer, newType) {
transformSpec.type = newType;
var newOptions = {};
fluid.each(transformSpec.options, function (path, oldKey) {
newOptions[path] = oldKey;
});
transformSpec.options = newOptions;
return transformSpec;
};
fluid.transforms.arrayToSetMembership.invert = function (transformSpec, transformer) {
return fluid.transforms.arrayToSetMembership.invertWithType(transformSpec, transformer,
"fluid.transforms.setMembershipToArray");
};
fluid.defaults("fluid.transforms.setMembershipToArray", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.setMembershipToArray.invert"
});
fluid.transforms.setMembershipToArray = function (input, transformSpec, transformer) {
var options = transformSpec.options;
if (!options) {
fluid.fail("setMembershipToArray requires an options block specified");
}
if (transformSpec.presentValue === undefined) {
transformSpec.presentValue = true;
}
if (transformSpec.missingValue === undefined) {
transformSpec.missingValue = false;
}
var outputArr = [];
fluid.each(options, function (outputVal, key) {
var value = fluid.get(input, key, transformer.resolverGetConfig);
if (value === transformSpec.presentValue) {
outputArr.push(outputVal);
}
});
return outputArr;
};
fluid.transforms.setMembershipToArray.invert = function (transformSpec, transformer) {
return fluid.transforms.arrayToSetMembership.invertWithType(transformSpec, transformer,
"fluid.transforms.arrayToSetMembership");
};
/* -------- deindexIntoArrayByKey and indexArrayByKey -------------------- */
/**
* Transforms the given array to an object.
* Uses the transformSpec.options.key values from each object within the array as new keys.
*
* For example, with transformSpec.key = "name" and an input object like this:
*
* {
* b: [
* { name: b1, v: v1 },
* { name: b2, v: v2 }
* ]
* }
*
* The output will be:
* {
* b: {
* b1: {
* v: v1
* }
* },
* {
* b2: {
* v: v2
* }
* }
* }
*/
fluid.model.transform.applyPaths = function (operation, pathOp, paths) {
for (var i = 0; i < paths.length; ++i) {
if (operation === "push") {
pathOp.push(paths[i]);
} else {
pathOp.pop();
}
}
};
fluid.model.transform.expandInnerValues = function (inputPath, outputPath, transformer, innerValues) {
var inputPrefixOp = transformer.inputPrefixOp;
var outputPrefixOp = transformer.outputPrefixOp;
var apply = fluid.model.transform.applyPaths;
apply("push", inputPrefixOp, inputPath);
apply("push", outputPrefixOp, outputPath);
var expanded = {};
fluid.each(innerValues, function (innerValue) {
var expandedInner = transformer.expand(innerValue);
if (!fluid.isPrimitive(expandedInner)) {
$.extend(true, expanded, expandedInner);
} else {
expanded = expandedInner;
}
});
apply("pop", outputPrefixOp, outputPath);
apply("pop", inputPrefixOp, inputPath);
return expanded;
};
fluid.defaults("fluid.transforms.indexArrayByKey", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens" ],
invertConfiguration: "fluid.transforms.indexArrayByKey.invert"
});
/** Transforms an array of objects into an object of objects, by indexing using the option "key" which must be supplied within the transform specification.
* The key of each element will be taken from the value held in each each original object's member derived from the option value in "key" - this member should
* exist in each array element. The member with name agreeing with "key" and its value will be removed from each original object before inserting into the returned
* object.
* For example,
* <code>fluid.transforms.indexArrayByKey([{k: "e1", b: 1, c: 2}, {k: "e2", b: 2: c: 3}], {key: "k"})</code> will output the object
* <code>{e1: {b: 1, c: 2}, e2: {b: 2: c, 3}</code>
* Note: This transform frequently arises in the context of data which arose in XML form, which often represents "morally indexed" data in repeating array-like
* constructs where the indexing key is held, for example, in an attribute.
*/
fluid.transforms.indexArrayByKey = function (arr, transformSpec, transformer) {
if (transformSpec.key === undefined) {
fluid.fail("indexArrayByKey requires a 'key' option.", transformSpec);
}
if (!fluid.isArrayable(arr)) {
fluid.fail("indexArrayByKey didn't find array at inputPath.", transformSpec);
}
var newHash = {};
var pivot = transformSpec.key;
fluid.each(arr, function (v, k) {
// check that we have a pivot entry in the object and it's a valid type:
var newKey = v[pivot];
var keyType = typeof(newKey);
if (keyType !== "string" && keyType !== "boolean" && keyType !== "number") {
fluid.fail("indexArrayByKey encountered untransformable array due to missing or invalid key", v);
}
// use the value of the key element as key and use the remaining content as value
var content = fluid.copy(v);
delete content[pivot];
// fix sub Arrays if needed:
if (transformSpec.innerValue) {
content = fluid.model.transform.expandInnerValues([transformer.inputPrefix, transformSpec.inputPath, k.toString()],
[transformSpec.outputPath, newKey], transformer, transformSpec.innerValue);
}
newHash[newKey] = content;
});
return newHash;
};
fluid.transforms.indexArrayByKey.invert = function (transformSpec) {
transformSpec.type = "fluid.transforms.deindexIntoArrayByKey";
// invert transforms from innerValue as well:
// TODO: The Model Transformations framework should be capable of this, but right now the
// issue is that we use a "private contract" to operate the "innerValue" slot. We need to
// spend time thinking of how this should be formalised
if (transformSpec.innerValue) {
var innerValue = transformSpec.innerValue;
for (var i = 0; i < innerValue.length; ++i) {
innerValue[i] = fluid.model.transform.invertConfiguration(innerValue[i]);
}
}
return transformSpec;
};
fluid.defaults("fluid.transforms.deindexIntoArrayByKey", {
gradeNames: [ "fluid.standardTransformFunction", "fluid.lens" ],
invertConfiguration: "fluid.transforms.deindexIntoArrayByKey.invert"
});
/**
* Transforms an object of objects into an array of objects, by deindexing by the option "key" which must be supplied within the transform specification.
* The key of each object will become split out into a fresh value in each array element which will be given the key held in the transformSpec option "key".
* For example:
* <code>fluid.transforms.deindexIntoArrayByKey({e1: {b: 1, c: 2}, e2: {b: 2: c, 3}, {key: "k"})</code> will output the array
* <code>[{k: "e1", b: 1, c: 2}, {k: "e2", b: 2: c: 3}]</code>
*
* This performs the inverse transform of fluid.transforms.indexArrayByKey.
*/
fluid.transforms.deindexIntoArrayByKey = function (hash, transformSpec, transformer) {
if (transformSpec.key === undefined) {
fluid.fail("deindexIntoArrayByKey requires a \"key\" option.", transformSpec);
}
var newArray = [];
var pivot = transformSpec.key;
fluid.each(hash, function (v, k) {
var content = {};
content[pivot] = k;
if (transformSpec.innerValue) {
v = fluid.model.transform.expandInnerValues([transformSpec.inputPath, k], [transformSpec.outputPath, newArray.length.toString()],
transformer, transformSpec.innerValue);
}
$.extend(true, content, v);
newArray.push(content);
});
return newArray;
};
fluid.transforms.deindexIntoArrayByKey.invert = function (transformSpec) {
transformSpec.type = "fluid.transforms.indexArrayByKey";
// invert transforms from innerValue as well:
// TODO: The Model Transformations framework should be capable of this, but right now the
// issue is that we use a "private contract" to operate the "innerValue" slot. We need to
// spend time thinking of how this should be formalised
if (transformSpec.innerValue) {
var innerValue = transformSpec.innerValue;
for (var i = 0; i < innerValue.length; ++i) {
innerValue[i] = fluid.model.transform.invertConfiguration(innerValue[i]);
}
}
return transformSpec;
};
fluid.defaults("fluid.transforms.limitRange", {
gradeNames: "fluid.standardTransformFunction"
});
fluid.transforms.limitRange = function (value, transformSpec) {
var min = transformSpec.min;
if (min !== undefined) {
var excludeMin = transformSpec.excludeMin || 0;
min += excludeMin;
if (value < min) {
value = min;
}
}
var max = transformSpec.max;
if (max !== undefined) {
var excludeMax = transformSpec.excludeMax || 0;
max -= excludeMax;
if (value > max) {
value = max;
}
}
return value;
};
fluid.defaults("fluid.transforms.indexOf", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.indexOf.invert"
});
fluid.transforms.indexOf = function (value, transformSpec) {
// We do not allow a positive number as 'notFound' value, as it threatens invertibility
if (typeof (transformSpec.notFound) === "number" && transformSpec.notFound >= 0) {
fluid.fail("A positive number is not allowed as 'notFound' value for indexOf");
}
var offset = fluid.transforms.parseIndexationOffset(transformSpec.offset, "indexOf");
var array = fluid.makeArray(transformSpec.array);
var originalIndex = array.indexOf(value);
return originalIndex === -1 && transformSpec.notFound ? transformSpec.notFound : originalIndex + offset;
};
fluid.transforms.indexOf.invert = function (transformSpec, transformer) {
var togo = fluid.transforms.invertArrayIndexation(transformSpec, transformer);
togo.type = "fluid.transforms.dereference";
return togo;
};
fluid.defaults("fluid.transforms.dereference", {
gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
invertConfiguration: "fluid.transforms.dereference.invert"
});
fluid.transforms.dereference = function (value, transformSpec) {
if (typeof (value) !== "number") {
return undefined;
}
var offset = fluid.transforms.parseIndexationOffset(transformSpec.offset, "dereference");
var array = fluid.makeArray(transformSpec.array);
var index = value + offset;
return array[index];
};
fluid.transforms.dereference.invert = function (transformSpec, transformer) {
var togo = fluid.transforms.invertArrayIndexation(transformSpec, transformer);
togo.type = "fluid.transforms.indexOf";
return togo;
};
fluid.transforms.parseIndexationOffset = function (offset, transformName) {
var parsedOffset = 0;
if (offset !== undefined) {
parsedOffset = fluid.parseInteger(offset);
if (isNaN(parsedOffset)) {
fluid.fail(transformName + " requires the value of \"offset\" to be an integer or a string that can be converted to an integer. " + offset + " is invalid.");
}
}
return parsedOffset;
};
fluid.transforms.invertArrayIndexation = function (transformSpec) {
if (!isNaN(Number(transformSpec.offset))) {
transformSpec.offset = Number(transformSpec.offset) * (-1);
}
return transformSpec;
};
fluid.defaults("fluid.transforms.stringTemplate", {
gradeNames: "fluid.standardOutputTransformFunction"
});
fluid.transforms.stringTemplate = function (transformSpec) {
return fluid.stringTemplate(transformSpec.template, transformSpec.terms);
};
fluid.defaults("fluid.transforms.free", {
gradeNames: "fluid.transformFunction"
});
fluid.transforms.free = function (transformSpec) {
var args = fluid.makeArray(transformSpec.args);
return fluid.invokeGlobalFunction(transformSpec.func, args);
};
fluid.defaults("fluid.transforms.quantize", {
gradeNames: "fluid.standardTransformFunction"
});
/**
* Quantize function maps a continuous range into discrete values. Given an input, it will
* be matched into a discrete bucket and the corresponding output will be done.
*/
fluid.transforms.quantize = function (value, transformSpec, transform) {
if (!transformSpec.ranges || !transformSpec.ranges.length) {
fluid.fail("fluid.transforms.quantize should have a key called ranges containing an array defining ranges to quantize");
}
// TODO: error checking that upper bounds are all numbers and increasing
for (var i = 0; i < transformSpec.ranges.length; i++) {
var rangeSpec = transformSpec.ranges[i];
if (value <= rangeSpec.upperBound || rangeSpec.upperBound === undefined && value >= Number.NEGATIVE_INFINITY) {
return fluid.isPrimitive(rangeSpec.output) ? rangeSpec.output : transform.expand(rangeSpec.output);
}
}
};
/**
* inRange transformer checks whether a value is within a given range and returns true if it is,
* and false if it's not.
*
* The range is defined by the two inputs: "min" and "max" (both inclusive). If one of these inputs
* is not present it is considered -infinite and +infinite, respectively - In other words, if no
* `min` value is defined, any value below or equal to the given "max" value will result in true.
*/
fluid.defaults("fluid.transforms.inRange", {
gradeNames: "fluid.standardTransformFunction"
});
fluid.transforms.inRange = function (value, transformSpec) {
return (transformSpec.min === undefined || transformSpec.min <= value) &&
(transformSpec.max === undefined || transformSpec.max >= value) ? true : false;
};
})(jQuery, fluid_2_0_0);

73
lib/infusion/src/framework/core/js/ResourceLoader.js

@ -0,0 +1,73 @@
/*
Copyright 2011-2016 OCAD University
Copyright 2011 Lucendo Development Ltd.
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/**
* A configurable component to allow users to load multiple resources via AJAX requests.
* The resources can be localised by means of options `locale`, `defaultLocale`. Once all
* resources are loaded, the event `onResourceLoaded` will be fired, which can be used
* to time the creation of components dependent on the resources.
*
* @param {Object} options
*/
fluid.defaults("fluid.resourceLoader", {
gradeNames: ["fluid.component"],
listeners: {
"onCreate.loadResources": {
listener: "fluid.resourceLoader.loadResources",
args: ["{that}", {expander: {func: "{that}.resolveResources"}}]
}
},
defaultLocale: null,
locale: null,
terms: {}, // Must be supplied by integrators
resources: {}, // Must be supplied by integrators
resourceOptions: {},
// Unsupported, non-API option
invokers: {
transformURL: {
funcName: "fluid.stringTemplate",
args: ["{arguments}.0", "{that}.options.terms"]
},
resolveResources: {
funcName: "fluid.resourceLoader.resolveResources",
args: "{that}"
}
},
events: {
onResourcesLoaded: null
}
});
fluid.resourceLoader.resolveResources = function (that) {
var mapped = fluid.transform(that.options.resources, that.transformURL);
return fluid.transform(mapped, function (url) {
var resourceSpec = {url: url, forceCache: true, options: that.options.resourceOptions};
return $.extend(resourceSpec, fluid.filterKeys(that.options, ["defaultLocale", "locale"]));
});
};
fluid.resourceLoader.loadResources = function (that, resources) {
fluid.fetchResources(resources, function () {
that.resources = resources;
that.events.onResourcesLoaded.fire(resources);
});
};
})(jQuery, fluid_2_0_0);

623
lib/infusion/src/framework/core/js/jquery.keyboard-a11y.js

@ -0,0 +1,623 @@
/*
Copyright 2008-2010 University of Cambridge
Copyright 2008-2010 University of Toronto
Copyright 2010-2011 Lucendo Development Ltd.
Copyright 2010-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
var fluid = fluid || fluid_2_0_0;
(function ($, fluid) {
"use strict";
// $().fluid("selectable", args)
// $().fluid("selectable".that()
// $().fluid("pager.pagerBar", args)
// $().fluid("reorderer", options)
/** Create a "bridge" from code written in the Fluid standard "that-ist" style,
* to the standard JQuery UI plugin architecture specified at http://docs.jquery.com/UI/Guidelines .
* Every Fluid component corresponding to the top-level standard signature (JQueryable, options)
* will automatically convert idiomatically to the JQuery UI standard via this adapter.
* Any return value which is a primitive or array type will become the return value
* of the "bridged" function - however, where this function returns a general hash
* (object) this is interpreted as forming part of the Fluid "return that" pattern,
* and the function will instead be bridged to "return this" as per JQuery standard,
* permitting chaining to occur. However, as a courtesy, the particular "this" returned
* will be augmented with a function that() which will allow the original return
* value to be retrieved if desired.
* @param {String} name The name under which the "plugin space" is to be injected into
* JQuery
* @param {Object} peer The root of the namespace corresponding to the peer object.
*/
fluid.thatistBridge = function (name, peer) {
var togo = function (funcname) {
var segs = funcname.split(".");
var move = peer;
for (var i = 0; i < segs.length; ++i) {
move = move[segs[i]];
}
var args = [this];
if (arguments.length === 2) {
args = args.concat($.makeArray(arguments[1]));
}
var ret = move.apply(null, args);
this.that = function () {
return ret;
};
var type = typeof(ret);
return !ret || type === "string" || type === "number" || type === "boolean" ||
(ret && ret.length !== undefined) ? ret : this;
};
$.fn[name] = togo;
return togo;
};
fluid.thatistBridge("fluid", fluid);
fluid.thatistBridge("fluid_2_0_0", fluid_2_0_0);
/*************************************************************************
* Tabindex normalization - compensate for browser differences in naming
* and function of "tabindex" attribute and tabbing order.
*/
// -- Private functions --
var normalizeTabindexName = function () {
return $.browser.msie ? "tabIndex" : "tabindex";
};
var canHaveDefaultTabindex = function (elements) {
if (elements.length <= 0) {
return false;
}
return $(elements[0]).is("a, input, button, select, area, textarea, object");
};
var getValue = function (elements) {
if (elements.length <= 0) {
return undefined;
}
if (!fluid.tabindex.hasAttr(elements)) {
return canHaveDefaultTabindex(elements) ? Number(0) : undefined;
}
// Get the attribute and return it as a number value.
var value = elements.attr(normalizeTabindexName());
return Number(value);
};
var setValue = function (elements, toIndex) {
return elements.each(function (i, item) {
$(item).attr(normalizeTabindexName(), toIndex);
});
};
// -- Public API --
/**
* Gets the value of the tabindex attribute for the first item, or sets the tabindex value of all elements
* if toIndex is specified.
*
* @param {String|Number} toIndex
*/
fluid.tabindex = function (target, toIndex) {
target = $(target);
if (toIndex !== null && toIndex !== undefined) {
return setValue(target, toIndex);
} else {
return getValue(target);
}
};
/**
* Removes the tabindex attribute altogether from each element.
*/
fluid.tabindex.remove = function (target) {
target = $(target);
return target.each(function (i, item) {
$(item).removeAttr(normalizeTabindexName());
});
};
/**
* Determines if an element actually has a tabindex attribute present.
*/
fluid.tabindex.hasAttr = function (target) {
target = $(target);
if (target.length <= 0) {
return false;
}
var togo = target.map(
function () {
var attributeNode = this.getAttributeNode(normalizeTabindexName());
return attributeNode ? attributeNode.specified : false;
}
);
return togo.length === 1 ? togo[0] : togo;
};
/**
* Determines if an element either has a tabindex attribute or is naturally tab-focussable.
*/
fluid.tabindex.has = function (target) {
target = $(target);
return fluid.tabindex.hasAttr(target) || canHaveDefaultTabindex(target);
};
// Keyboard navigation
// Public, static constants needed by the rest of the library.
fluid.a11y = $.a11y || {};
fluid.a11y.orientation = {
HORIZONTAL: 0,
VERTICAL: 1,
BOTH: 2
};
var UP_DOWN_KEYMAP = {
next: $.ui.keyCode.DOWN,
previous: $.ui.keyCode.UP
};
var LEFT_RIGHT_KEYMAP = {
next: $.ui.keyCode.RIGHT,
previous: $.ui.keyCode.LEFT
};
// Private functions.
var unwrap = function (element) {
return element.jquery ? element[0] : element; // Unwrap the element if it's a jQuery.
};
var makeElementsTabFocussable = function (elements) {
// If each element doesn't have a tabindex, or has one set to a negative value, set it to 0.
elements.each(function (idx, item) {
item = $(item);
if (!item.fluid("tabindex.has") || item.fluid("tabindex") < 0) {
item.fluid("tabindex", 0);
}
});
};
// Public API.
/**
* Makes all matched elements available in the tab order by setting their tabindices to "0".
*/
fluid.tabbable = function (target) {
target = $(target);
makeElementsTabFocussable(target);
};
/***********************************************************************
* Selectable functionality - geometrising a set of nodes such that they
* can be navigated (by setting focus) using a set of directional keys
*/
var CONTEXT_KEY = "selectionContext";
var NO_SELECTION = -32768;
var cleanUpWhenLeavingContainer = function (selectionContext) {
if (selectionContext.activeItemIndex !== NO_SELECTION) {
if (selectionContext.options.onLeaveContainer) {
selectionContext.options.onLeaveContainer(
selectionContext.selectables[selectionContext.activeItemIndex]
);
} else if (selectionContext.options.onUnselect) {
selectionContext.options.onUnselect(
selectionContext.selectables[selectionContext.activeItemIndex]
);
}
}
if (!selectionContext.options.rememberSelectionState) {
selectionContext.activeItemIndex = NO_SELECTION;
}
};
/**
* Does the work of selecting an element and delegating to the client handler.
*/
var drawSelection = function (elementToSelect, handler) {
if (handler) {
handler(elementToSelect);
}
};
/**
* Does does the work of unselecting an element and delegating to the client handler.
*/
var eraseSelection = function (selectedElement, handler) {
if (handler && selectedElement) {
handler(selectedElement);
}
};
var unselectElement = function (selectedElement, selectionContext) {
eraseSelection(selectedElement, selectionContext.options.onUnselect);
};
var selectElement = function (elementToSelect, selectionContext) {
// It's possible that we're being called programmatically, in which case we should clear any previous selection.
unselectElement(selectionContext.selectedElement(), selectionContext);
elementToSelect = unwrap(elementToSelect);
var newIndex = selectionContext.selectables.index(elementToSelect);
// Next check if the element is a known selectable. If not, do nothing.
if (newIndex === -1) {
return;
}
// Select the new element.
selectionContext.activeItemIndex = newIndex;
drawSelection(elementToSelect, selectionContext.options.onSelect);
};
var selectableFocusHandler = function (selectionContext) {
return function (evt) {
// FLUID-3590: newer browsers (FF 3.6, Webkit 4) have a form of "bug" in that they will go bananas
// on attempting to move focus off an element which has tabindex dynamically set to -1.
$(evt.target).fluid("tabindex", 0);
selectElement(evt.target, selectionContext);
// Force focus not to bubble on some browsers.
return evt.stopPropagation();
};
};
var selectableBlurHandler = function (selectionContext) {
return function (evt) {
$(evt.target).fluid("tabindex", selectionContext.options.selectablesTabindex);
unselectElement(evt.target, selectionContext);
// Force blur not to bubble on some browsers.
return evt.stopPropagation();
};
};
var reifyIndex = function (sc_that) {
var elements = sc_that.selectables;
if (sc_that.activeItemIndex >= elements.length) {
sc_that.activeItemIndex = (sc_that.options.noWrap ? elements.length - 1 : 0);
}
if (sc_that.activeItemIndex < 0 && sc_that.activeItemIndex !== NO_SELECTION) {
sc_that.activeItemIndex = (sc_that.options.noWrap ? 0 : elements.length - 1);
}
if (sc_that.activeItemIndex >= 0) {
fluid.focus(elements[sc_that.activeItemIndex]);
}
};
var prepareShift = function (selectionContext) {
// FLUID-3590: FF 3.6 and Safari 4.x won't fire blur() when programmatically moving focus.
var selElm = selectionContext.selectedElement();
if (selElm) {
fluid.blur(selElm);
}
unselectElement(selectionContext.selectedElement(), selectionContext);
if (selectionContext.activeItemIndex === NO_SELECTION) {
selectionContext.activeItemIndex = -1;
}
};
var focusNextElement = function (selectionContext) {
prepareShift(selectionContext);
++selectionContext.activeItemIndex;
reifyIndex(selectionContext);
};
var focusPreviousElement = function (selectionContext) {
prepareShift(selectionContext);
--selectionContext.activeItemIndex;
reifyIndex(selectionContext);
};
var arrowKeyHandler = function (selectionContext, keyMap) {
return function (evt) {
if (evt.which === keyMap.next) {
focusNextElement(selectionContext);
evt.preventDefault();
} else if (evt.which === keyMap.previous) {
focusPreviousElement(selectionContext);
evt.preventDefault();
}
};
};
var getKeyMapForDirection = function (direction) {
// Determine the appropriate mapping for next and previous based on the specified direction.
var keyMap;
if (direction === fluid.a11y.orientation.HORIZONTAL) {
keyMap = LEFT_RIGHT_KEYMAP;
}
else if (direction === fluid.a11y.orientation.VERTICAL) {
// Assume vertical in any other case.
keyMap = UP_DOWN_KEYMAP;
}
return keyMap;
};
var tabKeyHandler = function (selectionContext) {
return function (evt) {
if (evt.which !== $.ui.keyCode.TAB) {
return;
}
cleanUpWhenLeavingContainer(selectionContext);
// Catch Shift-Tab and note that focus is on its way out of the container.
if (evt.shiftKey) {
selectionContext.focusIsLeavingContainer = true;
}
};
};
var containerFocusHandler = function (selectionContext) {
return function (evt) {
var shouldOrig = selectionContext.options.autoSelectFirstItem;
var shouldSelect = typeof(shouldOrig) === "function" ? shouldOrig() : shouldOrig;
// Override the autoselection if we're on the way out of the container.
if (selectionContext.focusIsLeavingContainer) {
shouldSelect = false;
}
// This target check works around the fact that sometimes focus bubbles, even though it shouldn't.
if (shouldSelect && evt.target === selectionContext.container.get(0)) {
if (selectionContext.activeItemIndex === NO_SELECTION) {
selectionContext.activeItemIndex = 0;
}
fluid.focus(selectionContext.selectables[selectionContext.activeItemIndex]);
}
// Force focus not to bubble on some browsers.
return evt.stopPropagation();
};
};
var containerBlurHandler = function (selectionContext) {
return function (evt) {
selectionContext.focusIsLeavingContainer = false;
// Force blur not to bubble on some browsers.
return evt.stopPropagation();
};
};
var makeElementsSelectable = function (container, defaults, userOptions) {
var options = $.extend(true, {}, defaults, userOptions);
var keyMap = getKeyMapForDirection(options.direction);
var selectableElements = options.selectableElements ? options.selectableElements :
container.find(options.selectableSelector);
// Context stores the currently active item(undefined to start) and list of selectables.
var that = {
container: container,
activeItemIndex: NO_SELECTION,
selectables: selectableElements,
focusIsLeavingContainer: false,
options: options
};
that.selectablesUpdated = function (focusedItem) {
// Remove selectables from the tab order and add focus/blur handlers
if (typeof(that.options.selectablesTabindex) === "number") {
that.selectables.fluid("tabindex", that.options.selectablesTabindex);
}
that.selectables.off("focus." + CONTEXT_KEY);
that.selectables.off("blur." + CONTEXT_KEY);
that.selectables.on("focus." + CONTEXT_KEY, selectableFocusHandler(that));
that.selectables.on("blur." + CONTEXT_KEY, selectableBlurHandler(that));
if (keyMap && that.options.noBubbleListeners) {
that.selectables.off("keydown." + CONTEXT_KEY);
that.selectables.on("keydown." + CONTEXT_KEY, arrowKeyHandler(that, keyMap));
}
if (focusedItem) {
selectElement(focusedItem, that);
}
else {
reifyIndex(that);
}
};
that.refresh = function () {
if (!that.options.selectableSelector) {
fluid.fail("Cannot refresh selectable context which was not initialised by a selector");
}
that.selectables = container.find(options.selectableSelector);
that.selectablesUpdated();
};
that.selectedElement = function () {
return that.activeItemIndex < 0 ? null : that.selectables[that.activeItemIndex];
};
// Add various handlers to the container.
if (keyMap && !that.options.noBubbleListeners) {
container.keydown(arrowKeyHandler(that, keyMap));
}
container.keydown(tabKeyHandler(that));
container.focus(containerFocusHandler(that));
container.blur(containerBlurHandler(that));
that.selectablesUpdated();
return that;
};
/**
* Makes all matched elements selectable with the arrow keys.
* Supply your own handlers object with onSelect: and onUnselect: properties for custom behaviour.
* Options provide configurability, including direction: and autoSelectFirstItem:
* Currently supported directions are jQuery.a11y.directions.HORIZONTAL and VERTICAL.
*/
fluid.selectable = function (target, options) {
target = $(target);
var that = makeElementsSelectable(target, fluid.selectable.defaults, options);
fluid.setScopedData(target, CONTEXT_KEY, that);
return that;
};
/**
* Selects the specified element.
*/
fluid.selectable.select = function (target, toSelect) {
fluid.focus(toSelect);
};
/**
* Selects the next matched element.
*/
fluid.selectable.selectNext = function (target) {
target = $(target);
focusNextElement(fluid.getScopedData(target, CONTEXT_KEY));
};
/**
* Selects the previous matched element.
*/
fluid.selectable.selectPrevious = function (target) {
target = $(target);
focusPreviousElement(fluid.getScopedData(target, CONTEXT_KEY));
};
/**
* Returns the currently selected item wrapped as a jQuery object.
*/
fluid.selectable.currentSelection = function (target) {
target = $(target);
var that = fluid.getScopedData(target, CONTEXT_KEY);
return $(that.selectedElement());
};
fluid.selectable.defaults = {
direction: fluid.a11y.orientation.VERTICAL,
selectablesTabindex: -1,
autoSelectFirstItem: true,
rememberSelectionState: true,
selectableSelector: ".selectable",
selectableElements: null,
onSelect: null,
onUnselect: null,
onLeaveContainer: null,
noWrap: false
};
/********************************************************************
* Activation functionality - declaratively associating actions with
* a set of keyboard bindings.
*/
var checkForModifier = function (binding, evt) {
// If no modifier was specified, just return true.
if (!binding.modifier) {
return true;
}
var modifierKey = binding.modifier;
var isCtrlKeyPresent = modifierKey && evt.ctrlKey;
var isAltKeyPresent = modifierKey && evt.altKey;
var isShiftKeyPresent = modifierKey && evt.shiftKey;
return isCtrlKeyPresent || isAltKeyPresent || isShiftKeyPresent;
};
/** Constructs a raw "keydown"-facing handler, given a binding entry. This
* checks whether the key event genuinely triggers the event and forwards it
* to any "activateHandler" registered in the binding.
*/
var makeActivationHandler = function (binding) {
return function (evt) {
var target = evt.target;
if (!fluid.enabled(target)) {
return;
}
// The following 'if' clause works in the real world, but there's a bug in the jQuery simulation
// that causes keyboard simulation to fail in Safari, causing our tests to fail:
// http://ui.jquery.com/bugs/ticket/3229
// The replacement 'if' clause works around this bug.
// When this issue is resolved, we should revert to the original clause.
// if (evt.which === binding.key && binding.activateHandler && checkForModifier(binding, evt)) {
var code = evt.which ? evt.which : evt.keyCode;
if (code === binding.key && binding.activateHandler && checkForModifier(binding, evt)) {
var event = $.Event("fluid-activate");
$(target).trigger(event, [binding.activateHandler]);
if (event.isDefaultPrevented()) {
evt.preventDefault();
}
}
};
};
var makeElementsActivatable = function (elements, onActivateHandler, defaultKeys, options) {
// Create bindings for each default key.
var bindings = [];
$(defaultKeys).each(function (index, key) {
bindings.push({
modifier: null,
key: key,
activateHandler: onActivateHandler
});
});
// Merge with any additional key bindings.
if (options && options.additionalBindings) {
bindings = bindings.concat(options.additionalBindings);
}
fluid.initEnablement(elements);
// Add listeners for each key binding.
for (var i = 0; i < bindings.length; ++i) {
var binding = bindings[i];
elements.keydown(makeActivationHandler(binding));
}
elements.on("fluid-activate", function (evt, handler) {
handler = handler || onActivateHandler;
return handler ? handler(evt) : null;
});
};
/**
* Makes all matched elements activatable with the Space and Enter keys.
* Provide your own handler function for custom behaviour.
* Options allow you to provide a list of additionalActivationKeys.
*/
fluid.activatable = function (target, fn, options) {
target = $(target);
makeElementsActivatable(target, fn, fluid.activatable.defaults.keys, options);
};
/**
* Activates the specified element.
*/
fluid.activate = function (target) {
$(target).trigger("fluid-activate");
};
// Public Defaults.
fluid.activatable.defaults = {
keys: [$.ui.keyCode.ENTER, $.ui.keyCode.SPACE]
};
})(jQuery, fluid_2_0_0);

152
lib/infusion/src/framework/core/js/jquery.standalone.js

@ -0,0 +1,152 @@
/*
* Definitions in this file taken from:
*
* jQuery JavaScript Library v1.6.1
* http://jquery.com/
*
* This implementation is only intended to be used in contexts where the Fluid Infusion framework
* is required to be used without a functioning DOM being available (node.js or other standalone contexts).
* It includes the minimum definitions taken from jQuery required to operate the core of Fluid.js
* without FluidView.js. Consult http://issues.fluidproject.org/browse/FLUID-4568 for more details.
*
* Copyright 2011, John Resig
* Copyright 2011- OCAD University
*
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
* Date: Thu May 12 15:04:36 2011 -0400
*/
/* global jQuery:true, global */
/* exported jQuery */
var fluid_2_0_0 = fluid_2_0_0 || {};
var fluid = fluid || fluid_2_0_0;
(function (fluid) {
"use strict";
// Save a reference to some core methods
var toString = Object.prototype.toString;
var hasOwn = Object.prototype.hasOwnProperty;
var globalScope = typeof window !== "undefined" ? window :
typeof self !== "undefined" ? self : global;
// Map over jQuery in case of overwrite
var _jQuery = globalScope.jQuery;
// Map over the $ in case of overwrite
var _$ = globalScope.$;
var jQuery = fluid.jQueryStandalone = {
// The current version of jQuery being used
jquery: "1.6.1-fluidStandalone",
noConflict: function (deep) {
if (globalScope.$ === jQuery) {
globalScope.$ = _$;
}
if (deep && globalScope.jQuery === jQuery) {
globalScope.jQuery = _jQuery;
}
return jQuery;
},
isArray: Array.isArray || function (obj) {
return toString.call(obj) === "[object Array]";
},
// A crude way of determining if an object is a window
isWindow: function (obj) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
isPlainObject: function (obj) {
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
// Not own constructor property must be Object
if ( obj.constructor &&
!hasOwn.call(obj, "constructor") &&
!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
// TODO: Isn't this enormously expensive?
var key;
for (key in obj) {} // eslint-disable-line no-empty
return key === undefined || hasOwn.call( obj, key );
},
trim: function (str) {
return str.trim();
},
isEmptyObject: function (obj) {
for ( var name in obj ) { // eslint-disable-line no-unused-vars
return false;
}
return true;
},
extend: function () {
var options,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== "object" && typeof(target) !== "function") {
target = {};
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) !== null ) {
// Extend the base object
for (var name in options) {
var src = target[ name ];
var copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
var copyIsArray, clone;
// Recurse if we're merging plain objects or arrays
if (deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy))) ) {
if (copyIsArray) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = jQuery.extend( deep, clone, copy );
} else if (copy !== undefined) {
// Don't bring in undefined values
target[name] = copy;
}
}
}
}
return target;
}
};
})(fluid_2_0_0);
var jQuery = fluid.jQueryStandalone;

4
lib/infusion/src/framework/enhancement/css/ProgressiveEnhancement.css

@ -0,0 +1,4 @@
/* Progressive Enhancement: JS will reverse the display setup if it is enabled */
.fl-progEnhance-enhanced {display:none}
.fl-progEnhance-basic {}

14
lib/infusion/src/framework/enhancement/enhancementDependencies.json

@ -0,0 +1,14 @@
{
"enhancement": {
"name": "Progressive Enhancement",
"description": "Provides support for progressively enhancing components based on the capabilities of the browser.",
"files": [
"./js/ContextAwareness.js",
"./js/ProgressiveEnhancement.js"
],
"dependencies": [
"jQuery",
"framework"
]
}
}

221
lib/infusion/src/framework/enhancement/js/ContextAwareness.js

@ -0,0 +1,221 @@
/*
Copyright 2008-2009 University of Toronto
Copyright 2010-2016 OCAD University
Copyright 2015-2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.contextAware");
fluid.defaults("fluid.contextAware.marker", {
gradeNames: ["fluid.component"]
});
// unsupported, NON-API function
fluid.contextAware.makeCheckMarkers = function (checks, path, instantiator) {
fluid.each(checks, function (value, markerTypeName) {
fluid.constructSingle(path, {
type: markerTypeName,
gradeNames: "fluid.contextAware.marker",
value: value
}, instantiator);
});
};
/** Peforms the computation for `fluid.contextAware.makeChecks` and returns a structure suitable for being sent to `fluid.contextAware.makeCheckMarkers` -
*
* @return A hash of marker type names to grade names - this can be sent to fluid.contextAware.makeCheckMarkers
*/
// unsupported, NON-API function
fluid.contextAware.performChecks = function (checkHash) {
return fluid.transform(checkHash, function (checkRecord) {
if (typeof(checkRecord) === "function") {
checkRecord = {func: checkRecord};
} else if (typeof(checkRecord) === "string") {
checkRecord = {funcName: checkRecord};
}
if (fluid.isPrimitive(checkRecord)) {
return checkRecord;
} else if ("value" in checkRecord) {
return checkRecord.value;
} else if ("func" in checkRecord) {
return checkRecord.func();
} else if ("funcName" in checkRecord) {
return fluid.invokeGlobalFunction(checkRecord.funcName);
} else {
fluid.fail("Error in contextAwareness check record ", checkRecord, " - must contain an entry with name value, func, or funcName");
}
});
};
/**
* Takes an object whose keys are check context names and whose values are check records, designating a collection of context markers which might be registered at a location
* in the component tree.
* @param checkHash {Object} The keys in this structure are the context names to be supplied if the check passes, and the values are check records.
* A check record contains:
* ONE OF:
* value {Any} [optional] A literal value name to be attached to the context
* func {Function} [optional] A zero-arg function to be called to compute the value
* funcName {String} [optional] The name of a zero-arg global function which will compute the value
* If the check record consists of a Number or Boolean, it is assumed to be the value given to "value".
* @param path {String|Array} [optional] The path in the component tree at which the check markers are to be registered. If omitted, "" is assumed
* @param instantiator {Instantiator} [optional] The instantiator holding the component tree which will receive the markers. If omitted, use `fluid.globalInstantiator`.
*/
fluid.contextAware.makeChecks = function (checkHash, path, instantiator) {
var checkOptions = fluid.contextAware.performChecks(checkHash);
fluid.contextAware.makeCheckMarkers(checkOptions, path, instantiator);
};
/**
* Forgets a check made at a particular level of the component tree.
* @param markerNames {Array of String} The marker typeNames whose check values are to be forgotten
* @param path {String|Array} [optional] The path in the component tree at which the check markers are to be removed. If omitted, "" is assumed
* @param instantiator {Instantiator} [optional] The instantiator holding the component tree the markers are to be removed from. If omitted, use `fluid.globalInstantiator`.
*/
fluid.contextAware.forgetChecks = function (markerNames, path, instantiator) {
instantiator = instantiator || fluid.globalInstantiator;
path = path || [];
var markerArray = fluid.makeArray(markerNames);
fluid.each(markerArray, function (markerName) {
var memberName = fluid.typeNameToMemberName(markerName);
var segs = fluid.model.parseToSegments(path, instantiator.parseEL, true);
segs.push(memberName);
fluid.destroy(segs, instantiator);
});
};
/** A grade to be given to a component which requires context-aware adaptation.
* This grade consumes configuration held in the block named "contextAwareness", which is an object whose keys are check namespaces and whose values hold
* sequences of "checks" to be made in the component tree above the component. The value searched by
* each check is encoded as the element named `contextValue` - this either represents an IoC reference to a component
* or a particular value held at the component. If this reference has no path component, the path ".options.value" will be assumed.
* These checks seek contexts which
* have been previously registered using fluid.contextAware.makeChecks. The first context which matches
* with a value of `true` terminates the search, and returns by applying the grade names held in `gradeNames` to the current component.
* If no check matches, the grades held in `defaultGradeNames` will be applied.
*/
fluid.defaults("fluid.contextAware", {
gradeNames: ["{that}.check"],
mergePolicy: {
contextAwareness: "noexpand"
},
contextAwareness: {
// Hash of names (check namespaces) to records: {
// checks: {}, // Hash of check namespace to: {
// contextValue: IoCExpression testing value in environment,
// gradeNames: gradeNames which will be output,
// priority: String/Number for priority of check [optional]
// equals: Value to be compared to contextValue [optional - default is `true`]
// defaultGradeNames: // String or Array of String holding default gradeNames which will be output if no check matches [optional]
// priority: // Number or String encoding priority relative to other records (same format as with event listeners) [optional]
// }
},
invokers: {
check: {
funcName: "fluid.contextAware.check",
args: ["{that}", "{that}.options.contextAwareness"]
}
}
});
fluid.contextAware.getCheckValue = function (that, reference) {
// cf. core of distributeOptions!
var targetRef = fluid.parseContextReference(reference);
var targetComponent = fluid.resolveContext(targetRef.context, that);
var path = targetRef.path || ["options", "value"];
var value = fluid.getForComponent(targetComponent, path);
return value;
};
// unsupported, NON-API function
fluid.contextAware.checkOne = function (that, contextAwareRecord) {
if (contextAwareRecord.checks && contextAwareRecord.checks.contextValue) {
fluid.fail("Nesting error in contextAwareness record ", contextAwareRecord, " - the \"checks\" entry must contain a hash and not a contextValue/gradeNames record at top level");
}
var checkList = fluid.parsePriorityRecords(contextAwareRecord.checks, "contextAwareness checkRecord");
return fluid.find(checkList, function (check) {
if (!check.contextValue) {
fluid.fail("Cannot perform check for contextAwareness record ", check, " without a valid field named \"contextValue\"");
}
var value = fluid.contextAware.getCheckValue(that, check.contextValue);
if (check.equals === undefined ? value : value === check.equals) {
return check.gradeNames;
}
}, contextAwareRecord.defaultGradeNames);
};
// unsupported, NON-API function
fluid.contextAware.check = function (that, contextAwarenessOptions) {
var gradeNames = [];
var contextAwareList = fluid.parsePriorityRecords(contextAwarenessOptions, "contextAwareness adaptationRecord");
fluid.each(contextAwareList, function (record) {
var matched = fluid.contextAware.checkOne(that, record);
gradeNames = gradeNames.concat(fluid.makeArray(matched));
});
return gradeNames;
};
/** Given a set of options, broadcast an adaptation to all instances of a particular component in a particular context. ("new demands blocks").
* This has the effect of fabricating a grade with a particular name with an options distribution to `{/ typeName}` for the required component,
* and then constructing a single well-known instance of it.
* Options layout:
* distributionName {String} A grade name - the name to be given to the fabricated grade
* targetName {String} A grade name - the name of the grade to receive the adaptation
* adaptationName {String} the name of the contextAwareness record to receive the record - this will be a simple string
* checkName {String} the name of the check within the contextAwareness record to receive the record - this will be a simple string
* record {Object} the record to be broadcast into contextAwareness - should contain entries
* contextValue {IoC expression} the context value to be checked to activate the adaptation
* gradeNames {String/Array of String} the grade names to be supplied to the adapting target (matching advisedName)
*/
fluid.contextAware.makeAdaptation = function (options) {
fluid.expect("fluid.contextAware.makeAdaptation", options, ["distributionName", "targetName", "adaptationName", "checkName", "record"]);
fluid.defaults(options.distributionName, {
gradeNames: ["fluid.component"],
distributeOptions: {
target: "{/ " + options.targetName + "}.options.contextAwareness." + options.adaptationName + ".checks." + options.checkName,
record: options.record
}
});
fluid.constructSingle([], options.distributionName);
};
// Context awareness for the browser environment
fluid.contextAware.isBrowser = function () {
return typeof(window) !== "undefined" && window.document;
};
fluid.contextAware.makeChecks({
"fluid.browser": {
funcName: "fluid.contextAware.isBrowser"
}
});
// Context awareness for the reported browser platform name (operating system)
fluid.registerNamespace("fluid.contextAware.browser");
fluid.contextAware.browser.getPlatformName = function () {
return typeof(navigator) !== "undefined" && navigator.platform ? navigator.platform : undefined;
};
fluid.contextAware.makeChecks({
"fluid.browser.platformName": {
funcName: "fluid.contextAware.browser.getPlatformName"
}
});
})(jQuery, fluid_2_0_0);

34
lib/infusion/src/framework/enhancement/js/ProgressiveEnhancement.js

@ -0,0 +1,34 @@
/*
Copyright 2008-2009 University of Toronto
Copyright 2010-2011 OCAD University
Copyright 2013 Raising the Floor - US
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.enhance");
/**********************************************************
* This code runs immediately upon inclusion of this file *
**********************************************************/
// Use JavaScript to hide any markup that is specifically in place for cases when JavaScript is off.
// Note: the use of fl-ProgEnhance-basic is deprecated, and replaced by fl-progEnhance-basic.
// It is included here for backward compatibility only.
// Distinguish the standalone jQuery from the real one so that this can be included in IoC standalone tests
if (fluid.contextAware.isBrowser() && $.fn) {
$("head").append("<style type='text/css'>.fl-progEnhance-basic, .fl-ProgEnhance-basic { display: none; } .fl-progEnhance-enhanced, .fl-ProgEnhance-enhanced { display: block; }</style>");
}
})(jQuery, fluid_2_0_0);

1
lib/infusion/src/framework/preferences/css/Enactors.css

File diff suppressed because one or more lines are too long

1
lib/infusion/src/framework/preferences/css/FullNoPreviewPrefsEditor.css

@ -0,0 +1 @@
body{max-width:60em;min-width:56em}.fl-prefsEditor-fullNoPreview .fl-prefsEditor-category{border-top:2px solid #ddd;clear:both;-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid;}.fl-prefsEditor-fullNoPreview .fl-prefsEditor-category h2 img{padding-top:10px;margin-bottom:-4px}.fl-prefsEditor-fullNoPreview .fl-prefsEditor-buttons{text-align:right}.fl-prefsEditor-fullNoPreview .fl-prefsEditors-controls{-webkit-columns:15.625rem 4;-moz-columns:15.625rem 4;columns:15.625rem 4}.fl-prefsEditor-fullNoPreview ul{float:left;}.fl-prefsEditor-fullNoPreview ul li{clear:left}.fl-prefsEditor-fullNoPreview label,.fl-prefsEditor-fullNoPreview .fl-inputs{margin-bottom:15px}

1
lib/infusion/src/framework/preferences/css/FullPrefsEditor.css

@ -0,0 +1 @@
.fl-prefsEditor h2{font-size:1.29em;font-weight:bold;clear:both;color:#444;padding:14px 0 5px 25px}.fl-prefsEditor .fl-prefsEditor-buttons{margin-top:1em;float:left;width:100%;}.fl-prefsEditor .fl-prefsEditor-buttons input{text-transform:uppercase;text-decoration:underline;font-weight:bold;border:2px solid #cecece;font-size:1em;padding:5px;height:2.5em;margin-right:3px;background-color:#f0f0f0}.fl-prefsEditor h2 img{margin-bottom:-3px;padding-right:5px}.fl-prefsEditor .fl-prefsEditor-text ul li{clear:left;height:3.5em}.fl-prefsEditor .fl-slider-input{float:right}

1
lib/infusion/src/framework/preferences/css/FullPreviewPrefsEditor.css

@ -0,0 +1 @@
.fl-prefsEditor-fullPreview{min-width:800px;}.fl-prefsEditor-fullPreview ul li{clear:left}.fl-prefsEditor-fullPreview .fl-prefsEditor-fullPreview-adjusters{width:60%}.fl-prefsEditor-fullPreview .fl-prefsEditor-fullPreview-previewContainer{width:35%}.fl-prefsEditor-fullPreview h2{border-top:2px solid #ddd;margin-bottom:20px}.fl-prefsEditor-fullPreview .fl-prefsEditor-category{padding-bottom:20px}.fl-prefsEditor-fullPreview ul{margin-left:0;}.fl-prefsEditor-fullPreview ul li{list-style:none;width:100%}.fl-prefsEditor-fullPreview .fl-slider{float:left}.fl-prefsEditor-fullPreview label{display:block;width:16em}.fl-prefsEditor-fullPreview .fl-prefsEditor-layout li,.fl-prefsEditor-fullPreview .fl-prefsEditor-links li{padding-bottom:10px}.fl-prefsEditor-fullPreview .fl-prefsEditor-preview iframe{height:480px;width:100%;border:2px solid #d7d7d7}.fl-prefsEditor-fullPreview .fl-prefsEditor-preview h2{font-weight:lighter;font-size:1.8em;color:#4d4d4d;padding:18px 0 0 0}.fl-prefsEditor .fl-inputs{float:left}.fl-prefsEditor input[type="text"]{width:2em}

1
lib/infusion/src/framework/preferences/css/PrefsEditor.css

File diff suppressed because one or more lines are too long

1
lib/infusion/src/framework/preferences/css/SeparatedPanelPrefsEditor.css

@ -0,0 +1 @@
@font-face{font-family:'OpenSans';src:url("../../../lib/fonts/OpenSans-Light.ttf");font-weight:300;font-style:normal}@font-face{font-family:'OpenSans';src:url("../../../lib/fonts/OpenSans-Regular.ttf");font-weight:normal;font-style:normal}@font-face{font-family:'OpenSans';src:url("../../../lib/fonts/OpenSans-Semibold.ttf");font-weight:600;font-style:normal}.fl-prefsEditor-separatedPanel .fl-panelBar{width:100%;background-color:#fff;border-bottom:1px solid #ccc;}.fl-prefsEditor-separatedPanel .fl-panelBar .fl-prefsEditor-buttons{box-shadow:2px 2px 3px 0 #6f6f6f;border-bottom:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid #ccc;border-bottom-left-radius:8px;border-bottom-right-radius:8px;background-color:#fff;min-height:2em;margin-right:10em;display:block;float:right}.fl-prefsEditor-separatedPanel .fl-prefsEditor-showHide{min-width:15em}.fl-prefsEditor-separatedPanel .fl-prefsEditor-showHide,.fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{text-transform:lowercase;font-weight:bold;border:0;background-color:#fff;height:1.8em;color:#2f2b2a;font-family:'OpenSans',"Myriad Pro",Helvetica,Arial,sans-serif;font-size:1.1em;margin-right:.5em;margin-left:.5em}.fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{border-right:1px solid #ccc}.fl-prefsEditor-separatedPanel .fl-icon-undo{font-family:'InfusionIcons' !important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;float:left;margin-right:.3em;margin-top:-.1em;font-size:1em}.fl-prefsEditor-separatedPanel .fl-icon-undo:before{content:"\e008"}.fl-prefsEditor-separatedPanel-iframe{border:0;background-color:#fff;height:0;display:block;width:100%}.fl-theme-bw{background-image:none !important;background-color:#fff;border-color:#000;color:#000;}.fl-theme-bw .fl-prefsEditor-separatedPanel .fl-panelBar,.fl-theme-bw .fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{background-color:#fff;border-color:#000;color:#000}.fl-theme-bw .fl-prefsEditor-separatedPanel .fl-prefsEditor-buttons{box-shadow:none}.fl-theme-wb{background-image:none !important;background-color:#000;border-color:#fff;color:#fff;}.fl-theme-wb .fl-prefsEditor-separatedPanel .fl-panelBar,.fl-theme-wb .fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{background-color:#000;border-color:#fff;color:#fff}.fl-theme-wb .fl-prefsEditor-separatedPanel .fl-prefsEditor-buttons{box-shadow:none}.fl-theme-yb{background-image:none !important;background-color:#000;border-color:#ff0;color:#ff0;}.fl-theme-yb .fl-prefsEditor-separatedPanel .fl-panelBar,.fl-theme-yb .fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{background-color:#000;border-color:#ff0;color:#ff0}.fl-theme-yb .fl-prefsEditor-separatedPanel .fl-prefsEditor-buttons{box-shadow:none}.fl-theme-by{background-image:none !important;background-color:#ff0;border-color:#000;color:#000;}.fl-theme-by .fl-prefsEditor-separatedPanel .fl-panelBar,.fl-theme-by .fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{background-color:#ff0;border-color:#000;color:#000}.fl-theme-by .fl-prefsEditor-separatedPanel .fl-prefsEditor-buttons{box-shadow:none}.fl-theme-lgdg{background-image:none !important;background-color:#555;border-color:#bdbdbb;color:#bdbdbb;}.fl-theme-lgdg .fl-prefsEditor-separatedPanel .fl-panelBar,.fl-theme-lgdg .fl-prefsEditor-separatedPanel .fl-prefsEditor-reset{background-color:#555;border-color:#bdbdbb;color:#bdbdbb}.fl-theme-lgdg .fl-prefsEditor-separatedPanel .fl-prefsEditor-buttons{box-shadow:none}

1
lib/infusion/src/framework/preferences/css/SeparatedPanelPrefsEditorFrame.css

@ -0,0 +1 @@
@font-face{font-family:"OpenSans";src:url("../../../lib/fonts/OpenSans-Light.ttf");font-weight:300;font-style:normal}@font-face{font-family:'OpenSans';src:url("../../../lib/fonts/OpenSans-Regular.ttf");font-weight:normal;font-style:normal}@font-face{font-family:'OpenSans';src:url("../../../lib/fonts/OpenSans-Semibold.ttf");font-weight:600;font-style:normal}html{overflow:visible}.fl-prefsEditor-separatedPanel{background-color:#fff;background-image:url("../images/default/separatedpanelbg.png");background-repeat:repeat;height:100%;overflow-x:auto;overflow-y:hidden;color:#766d68;padding:1px 0;margin:0;}.fl-prefsEditor-separatedPanel .fl-prefsEditor{font-family:'OpenSans',"Myriad Pro",Helvetica,Arial,sans-serif;line-height:1.2em !important}.fl-prefsEditor-separatedPanel .fl-prefsEditor-panels{white-space:nowrap;display:table;border-spacing:20px 0;}.fl-prefsEditor-separatedPanel .fl-prefsEditor-panels .fl-prefsEditor-panel{border:1px solid #615e59;border-radius:5px;box-shadow:2px 2px #7a766d;display:inline-block;padding:1em;min-width:25em;background-color:#fff;white-space:normal;display:table-cell}.fl-prefsEditor-separatedPanel h2{color:#766d68;padding:0 0 .5em 0;margin:0 0 1.5em 0;width:100%;border-bottom:solid #ccc 1px;box-shadow:0 6px 7px -6px rgba(0,0,0,0.3);font-size:1.2em;text-transform:lowercase;line-height:1.7em;height:2em}.fl-prefsEditor-separatedPanel .fl-inputs{float:none;padding:0}.fl-prefsEditor-separatedPanel label{font-size:1em;display:block}.fl-prefsEditor-separatedPanel .fl-links-buttons-options label{width:21em;display:inline-block}.fl-prefsEditor-separatedPanel .fl-links-buttons-options li{margin-bottom:.4em}.fl-prefsEditor-separatedPanel h2 label,.fl-prefsEditor-separatedPanel h2 .fl-heading-text{float:left;padding-top:.2em;font-size:1.3em;font-weight:600}.fl-prefsEditor-separatedPanel.fl-theme-bw{background-image:none}.fl-prefsEditor-separatedPanel.fl-theme-wb{background-image:none}.fl-prefsEditor-separatedPanel.fl-theme-yb{background-image:none}.fl-prefsEditor-separatedPanel.fl-theme-by{background-image:none}.fl-prefsEditor-separatedPanel.fl-theme-lgdg{background-image:none}

42
lib/infusion/src/framework/preferences/css/stylus/Enactors.styl

@ -0,0 +1,42 @@
/*
* General prefs editor styling used to apply preferences
* !important is used for all styles to ensure that Preferences Framework's enactors are able to override the page's default styles.
*/
@import "utils/Themes"
.fl-font-arial,
.fl-font-arial * {
font-family: "Arial" !important;
}
.fl-font-verdana,
.fl-font-verdana * {
font-family: "Verdana" !important;
}
.fl-font-times,
.fl-font-times * {
font-family: Georgia, Times, "Times New Roman", serif !important;
}
.fl-font-comic-sans,
.fl-font-comic-sans * {
font-family: "Comic Sans MS", sans-serif !important;
}
// UI Enhancer "Enlarge text" link options
.fl-text-larger input,
.fl-text-larger button {
font-size: 125% !important;
}
// UI Enhancer 'enhanced links' styling
.fl-link-enhanced a {
font-size: 125% !important;
text-decoration: underline !important;
font-weight: bold !important;
}
// Theming
build-themes-Enactors(themes);

47
lib/infusion/src/framework/preferences/css/stylus/FullNoPreviewPrefsEditor.styl

@ -0,0 +1,47 @@
// UI Option styles for Full page without preview
body {
max-width: 60em;
min-width: 56em;
}
.fl-prefsEditor-fullNoPreview {
// Category separator
.fl-prefsEditor-category {
border-top: 2px solid #dddddd;
clear: both;
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
// Section header icons
h2 img {
padding-top: 10px;
margin-bottom: -4px;
}
}
// Save, reset, cancel buttons
.fl-prefsEditor-buttons {
text-align: right;
}
// Controls
.fl-prefsEditors-controls {
-webkit-columns: 15.625rem 4;
-moz-columns: 15.625rem 4;
columns: 15.625rem 4;
}
ul {
float: left;
li {
clear: left;
}
}
label, .fl-inputs {
margin-bottom: 15px; // adds space between label, inputs when browser window is narrow
}
}

47
lib/infusion/src/framework/preferences/css/stylus/FullPrefsEditor.styl

@ -0,0 +1,47 @@
// UI Option styles for full page layouts both with and without preview
// Control section headers
.fl-prefsEditor {
h2 {
font-size: 1.29em;
font-weight: bold;
clear: both;
color: #444444;
padding: 14px 0px 5px 25px;
}
// Save, reset, cancel buttons
.fl-prefsEditor-buttons {
margin-top: 1em;
float: left;
width: 100%;
input {
text-transform: uppercase;
text-decoration: underline;
font-weight: bold;
border: 2px solid #cecece;
font-size: 1em;
padding: 5px;
height: 2.5em;
margin-right: 3px;
background-color: #F0F0F0;
}
}
// Section header icons
h2 img {
margin-bottom: -3px;
padding-right: 5px;
}
// Text controls
.fl-prefsEditor-text ul li {
clear: left;
height: 3.5em;
}
.fl-slider-input {
float: right;
}
}

75
lib/infusion/src/framework/preferences/css/stylus/FullPreviewPrefsEditor.styl

@ -0,0 +1,75 @@
// UI Option styles for full page layout with preview
.fl-prefsEditor-fullPreview {
min-width: 800px;
ul li {
clear: left;
}
.fl-prefsEditor-fullPreview-adjusters {
width: 60%;
}
.fl-prefsEditor-fullPreview-previewContainer {
width: 35%;
}
// Control section headers
h2 {
border-top: 2px solid #dddddd;
margin-bottom: 20px;
}
.fl-prefsEditor-category {
padding-bottom: 20px;
}
// Controls
ul {
margin-left: 0px;
li {
list-style: none;
width: 100%;
}
}
// Slider
.fl-slider {
float:left;
}
// Text controls
label {
display: block;
width: 16em;
}
// Layout and link controls
.fl-prefsEditor-layout li, .fl-prefsEditor-links li {
padding-bottom: 10px;
}
// Preview Area
.fl-prefsEditor-preview iframe {
height: 480px;
width: 100%;
border: 2px solid #d7d7d7;
}
.fl-prefsEditor-preview h2 {
font-weight: lighter;
font-size: 1.8em;
color: #4D4D4D;
padding: 18px 0px 0px 0px;
}
}
.fl-prefsEditor .fl-inputs {
float: left;
}
.fl-prefsEditor input[type="text"] {
width: 2em;
}

420
lib/infusion/src/framework/preferences/css/stylus/PrefsEditor.styl

@ -0,0 +1,420 @@
/* General Preferences Editor styles, used for all layouts */
@import "utils/Helpers"
@import "utils/Themes"
@font-face {
font-family: 'InfusionIcons';
src: url('../fonts/InfusionIcons-PrefsEditor.ttf'),
url('../fonts/InfusionIcons-PrefsEditor.eot');
}
.fl-prefsEditor {
font-family: "Myriad Pro", Helvetica, Arial, sans-serif;
// Controls
.fl-prefsEditor-panels {
margin-left: 0;
padding: 0;
li {
list-style: none;
}
}
label {
font-size: 1.2em;
}
input {
&[type="text"] {
width:2em;
font-size: 1em;
}
// Check boxes
&[type="checkbox"] {
margin-right:10px;
border: 1px solid black;
}
}
// Fieldset (remove default browser visual styling)
fieldset {
border: 0 transparent;
margin: 0;
padding: 0;
}
// Legend
legend {
padding: 0;
margin: 0 0 1em 0;
display: block;
}
// Drop downs
select {
border: 2px solid #ebebeb;
}
select {
&#textFont {
font-weight:bold;
option {
&.times {
font-family: "Times New Roman";
}
&.comic {
font-family: "Comic Sans MS";
}
&.arial {
font-family: Arial;
}
&.verdana {
font-family: Verdana;
}
}
}
&#theme {
font-weight:bold;
text-transform: uppercase;
option.fl-prefsEditor-default-theme {
color: #000 !important;
background-color: #efefef !important;
}
}
}
// Text and display controls
.fl-prefsEditor-text {
label {
display: block;
}
.fl-inputs {
float: left;
font-size: 1em;
}
}
.fl-prefsEditor-layout div,
.fl-prefsEditor-links div {
margin-left: 25px;
}
// Sliders
.fl-slider {
float:left;
border: 1px solid #b3b3b3;
border-radius: 0.3em;
background-color: #dad6d3;
padding: 0;
position: relative;
height: 1em;
margin-top: 0;
margin-left: 0.8em;
margin-right: 1.8em;
a, .fl-slider-handle {
position:absolute;
display:block;
top:0;
left:0;
margin-top: -0.4em;
margin-left: -0.4em;
slider-thumb-common();
&:active {
outline: none;
}
}
}
// Native slider appearance
// based on techniques described at https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/
// and http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html
input[type=range].fl-slider {
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
// Native slider thumb appearance
&::-webkit-slider-thumb {
-webkit-appearance: none;
slider-thumb-common();
}
&::-moz-range-thumb {
slider-thumb-common();
}
&::-ms-thumb {
slider-thumb-common();
}
// Native slider track appearance
// We hide any inherent track styling and let the styles
// set with the fl-slider class be used without interference
// from native widget styling
&::-webkit-slider-runnable-track {
slider-native-track-common();
}
&::-moz-range-track {
slider-native-track-common();
}
&::-ms-track {
slider-native-track-common();
color: transparent;
}
&::-ms-fill-lower {
background: none;
background-color: transparent;
border: 0;
}
&::-ms-fill-upper {
background: none;
background-color: transparent;
border: 0;
}
}
.fl-slider-horz {
width: 8.8em;
}
.fl-slider-input {
margin-left: 1em;
float:left;
}
.fl-slider-range {
background-color: #62bb3d;
height: 100%;
}
.fl-inputs div[class*='fl-icon-'] {
font-size: 1.5em;
margin-top: -0.15em;
}
// Theme radio buttons
.fl-choice {
display: inline;
float: left;
label {
margin-right: 5px;
border: 1px solid black;
border-radius: 5px;
height: 3em;
width: 3em;
text-align: center;
vertical-align: middle;
display: inline-block;
line-height: 3em !important;
padding: 2px;
}
.fl-preview-A {
font-size: 2em;
}
// Pseudo content to prevent AT from reading display 'a'
.fl-preview-A:before {
content: "a";
}
input:focus ~ label {
outline: 2px solid black;
}
}
// ON/OFF Switch for checkboxes
// The container for the toggle, which is also a label for the hidden checkbox.
.fl-prefsEditor-onoff {
.fl-prefsEditor-switch {
border-radius:50px;
border: 1px solid #776D67;
width: 5em;
height: 2em;
background-color: #E6E6E6;
box-shadow: 1em 1.1em 0 0 rgba(250,250,250,0.53) inset;
overflow: hidden;
vertical-align: middle;
display: block;
margin-top: 1em;
transition-duration: 0.2s;
transition-property: padding-left, width, background-color, margin-left;
font-size: 1.2em;
font-weight: 600;
text-transform: lowercase;
}
// Hide input while still being screen reader accessible
input {
border: 0 none;
clip: rect(0px, 0px, 0px, 0px);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
input:focus + label {
outline: 2px solid black;
}
input:checked + label .fl-prefsEditor-switch {
padding-left: 3em;
width: 2em;
background-color: #2da750;
box-shadow: -1em 1.1em 0.1em 0 rgba(172, 216, 92, 0.63) inset;
.fl-prefsEditor-switch-inner {
top:-1.6em;
left: 0.46em;
}
}
// With data attributes defining the on/off text, localizing the templates is easier
input + label {
[data-checkboxStateOn]:before {
content: attr(data-checkboxStateOn);
}
[data-checkboxStateOff]:after {
content: attr(data-checkboxStateOff);
}
// The "on" portion of the toggle and the knob.
.fl-prefsEditor-switch {
&:before {
color: #fff;
border: 1px solid #776D67;
border-radius: 50px;
float:left;
width:1.9em;
height: 1.9em; // Width and height of the knob.
text-indent:-1.6em;
line-height:1.7em;
text-shadow: 1px 1px 1px #000;
background-color: #fff;
box-shadow: 0.2em 0.2em 0.5em #888;
background-image: linear-gradient(bottom, rgb(205,204,202) 0%, rgb(244,244,244) 100%);
}
// The "off" portion of the toggle.
&:after {
float: left;
position: relative;
top: 0.36em;
left: 0.5em;
}
}
}
// The dark inner circle to the toggle knob.
.fl-prefsEditor-switch-inner {
border: 1px solid #493A30;
border-radius: 50px;
width: 1em;
height: 1em;
position: relative;
left: -2.85em;
top: 0.46em;
background-color: #675243;
box-shadow: 0 -0.2em 0.3em 0.05em rgba(250, 250, 250, 0.3) inset;
display: inline-block;
}
}
// Font Icons
.fl-icon-indicator, .fl-icon-crossout, .fl-icon-big-a, .fl-icon-small-a, .fl-icon-line-space-expanded, .fl-icon-line-space-condensed,
.fl-icon-contrast, .fl-icon-undo, .fl-icon-line-space, .fl-icon-links, .fl-icon-simplify, .fl-icon-font, .fl-icon-size, .fl-icon-text-to-speech, .fl-icon-toc {
font-family: 'InfusionIcons' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
-webkit-font-smoothing: antialiased;
float: left;
margin: 0.2em 0.3em 0 0;
font-size: 1.5em;
}
// control icons
.fl-icon-crossout:before, .fl-choice .fl-prefsEditor-contrast-defaultThemeLabel .fl-crossout:before {
content: "\e004";
}
.fl-icon-big-a:before {
content: "\e006";
}
.fl-icon-small-a:before {
content: "\e007";
}
.fl-icon-line-space-expanded:before {
content: "\e009";
}
.fl-icon-line-space-condensed:before {
content: "\e00a";
}
.fl-icon-indicator:before, .fl-choice input:checked + div:before {
content: "\e003";
}
.fl-choice {
.fl-indicator {
font-family: 'InfusionIcons' !important;
font-size: 1.3em;
margin: -0.75em 0 0 0.75em;
height: 1em;
}
.fl-prefsEditor-contrast-defaultThemeLabel .fl-crossout {
font-family: 'InfusionIcons' !important;
margin-top: -1.75em;
font-size: 1.85em;
}
label.fl-theme-prefsEditor-default,
label.fl-theme-prefsEditor-default span,
label.fl-prefsEditor-contrast-defaultThemeLabel .fl-crossout {
background-color: #FFFFFF !important;
border-color: #433F3D !important;
color: #433F3D !important;
}
}
// header icons
.fl-icon-contrast:before {
content: "\e005";
}
.fl-icon-line-space:before {
content: "\e00b";
}
.fl-icon-links:before {
content: "\e00c";
}
.fl-icon-simplify:before {
content: "\e00e";
}
.fl-icon-font:before {
content: "\e00f";
}
.fl-icon-size:before {
content: "\e010";
}
.fl-icon-text-to-speech:before {
content: "\e011";
}
.fl-icon-toc:before {
content: "\e012";
}
}
// Theming
build-themes-prefsEditor(themes);

26
lib/infusion/src/framework/preferences/css/stylus/README.md

@ -0,0 +1,26 @@
## Overview ##
The "[stylus](./)" directory contains Stylus files for generating the Preference Framework stylesheets.
### How to add a new theme ###
Define your own themes variable that looks like:
```
contrastThemes = {
"theme-selector": {
foregroundColor: #000000,
backgroundColor: #ffffff
}
...
}
```
When calling Stylus mixins defined in "[utils/Themes.styl](./utils/Themes.styl)", pass in your own themes variable as a parameter, such as:
```
build-themes-Enactors(contrastThemes);
```
### How to prevent grunt from compiling utility Stylus files ###
Some Stylus files may only contain mixins or functions for other Stylus files to import. Those files should not be compiled into CSS. To prevent the grunt task `grunt buildStylus` from compiling them, place these files in the "[utils](./utils)" directory.

97
lib/infusion/src/framework/preferences/css/stylus/SeparatedPanelPrefsEditor.styl

@ -0,0 +1,97 @@
@import "utils/Themes"
@font-face {
font-family: 'OpenSans';
src:url('../../../lib/fonts/OpenSans-Light.ttf');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src:url('../../../lib/fonts/OpenSans-Regular.ttf');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src:url('../../../lib/fonts/OpenSans-Semibold.ttf');
font-weight: 600;
font-style: normal;
}
// Top bar and show/hide button
.fl-prefsEditor-separatedPanel {
.fl-panelBar {
width: 100%;
background-color: #fff;
border-bottom: 1px solid #ccc;
.fl-prefsEditor-buttons {
box-shadow: 2px 2px 3px 0 #6f6f6f;
border-bottom: 1px solid #ccc;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc; //FLUID-4991: The shadow effect around the hide/show preferences bug fix. (Border right added)
border-bottom-left-radius:8px;
border-bottom-right-radius:8px;
background-color: white;
min-height: 2em;
margin-right: 10em;
display: block;
float: right;
}
}
.fl-prefsEditor-showHide {
min-width: 15em;
}
.fl-prefsEditor-showHide, .fl-prefsEditor-reset {
text-transform: lowercase;
font-weight: bold;
border: 0;
background-color: white;
height: 1.8em;
color: #2f2b2a;
font-family: 'OpenSans',"Myriad Pro", Helvetica, Arial, sans-serif;
font-size: 1.1em;
margin-right: 0.5em;
margin-left: 0.5em;
}
.fl-prefsEditor-reset {
border-right: 1px solid #ccc;
}
// Font icon for reset tab
.fl-icon-undo {
font-family: 'InfusionIcons' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
-webkit-font-smoothing: antialiased;
float: left;
margin-right: 0.3em;
margin-top: -0.1em;
font-size: 1em;
}
.fl-icon-undo:before {
content: "\e008";
}
}
// IFrame
.fl-prefsEditor-separatedPanel-iframe {
border: 0;
background-color: #fff;
height: 0px;
display: block;
width: 100%;
}
// Theming
build-themes-separatedPanelPrefsEditor(themes);

110
lib/infusion/src/framework/preferences/css/stylus/SeparatedPanelPrefsEditorFrame.styl

@ -0,0 +1,110 @@
@import "utils/Themes"
@font-face {
font-family: "OpenSans";
src: url("../../../lib/fonts/OpenSans-Light.ttf");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src:url('../../../lib/fonts/OpenSans-Regular.ttf');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'OpenSans';
src:url('../../../lib/fonts/OpenSans-Semibold.ttf');
font-weight: 600;
font-style: normal;
}
html {
overflow: visible
}
/* Preferences Editor Separated Panel styles */
.fl-prefsEditor-separatedPanel {
background-color: #fff;
background-image: url('../images/default/separatedpanelbg.png');
background-repeat: repeat;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
color: #766d68;
padding: 1px 0px;
margin: 0px;
.fl-prefsEditor {
font-family: 'OpenSans',"Myriad Pro", Helvetica, Arial, sans-serif;
line-height: 1.2em !important; // FLUID-4959: fix the line height so it doesn't scale with the Preferences Editor line space scaling.
}
// Preference Panels
.fl-prefsEditor-panels {
white-space: nowrap;
display: table;
border-spacing: 20px 0px;
.fl-prefsEditor-panel {
border: 1px solid #615e59;
border-radius: 5px;
box-shadow: 2px 2px #7a766d;
display: inline-block;
padding: 1em;
min-width: 25em;
background-color: #fff;
white-space: normal;
display: table-cell;
}
}
h2 {
color: #766d68;
padding: 0 0 0.5em 0;
margin: 0 0 1.5em 0;
width: 100%;
border-bottom: solid #ccc 1px;
box-shadow: 0px 6px 7px -6px rgba(0,0,0,0.3);
font-size: 1.2em;
text-transform: lowercase;
line-height:1.7em;
height: 2em;
}
.fl-inputs {
float: none;
padding: 0;
}
label {
font-size: 1em;
display: block;
}
// Emphasize links buttons panel
.fl-links-buttons-options {
label {
width: 21em;
display:inline-block;
}
li {
margin-bottom: 0.4em;
}
}
h2 {
label, .fl-heading-text {
float: left;
padding-top: 0.2em;
font-size: 1.3em;
font-weight: 600;
}
}
}
// Theming
build-themes-separatedPanelPrefsEditorFrame(themes);

43
lib/infusion/src/framework/preferences/css/stylus/utils/Helpers.styl

@ -0,0 +1,43 @@
// Basic stylus functions used by other stylus files
vendors = webkit moz o ms
linear-gradient() {
// Generate the gradient effect used by linear-gradient()
content = arguments[0];
count = 0;
for element in arguments {
if (count != 0) {
content = content, element;
}
count = count + 1;
}
if current-property {
for vendor in vendors {
add-property(current-property[0], s('-%s-linear-gradient(%s)', vendor, content));
}
s('linear-gradient(%s)', content);
}
}
// Mixin for common styles of the "thumb" (slider control)
// used by both the jQuery and native HTML sliders
slider-thumb-common() {
height: 1.5em;
width: 1.5em;
background-color: #fff;
border: 1px solid #b3b3b3;
border-radius: 2em;
box-shadow: 4px 2px 3px rgba(0,0,0,0.3);
background-image: linear-gradient(right top, #fff 46%, #e9eaea 73%);
}
// Mixin for common styles of the "track" on the native HTML sliders
slider-native-track-common() {
background: transparent;
border: 0px;
}

358
lib/infusion/src/framework/preferences/css/stylus/utils/Themes.styl

@ -0,0 +1,358 @@
// Contains utility functions for building contrast themes
// !important is used for all styles to ensure that Preferences Framework's enactors are able to override the pages default styles.
themes = {
".fl-theme-bw": {
foregroundColor: #000000,
backgroundColor: #ffffff
}
".fl-theme-wb": {
foregroundColor: #ffffff,
backgroundColor: #000000
}
".fl-theme-yb": {
foregroundColor: #ffff00,
backgroundColor: #000000
}
".fl-theme-by": {
foregroundColor: #000000,
backgroundColor: #ffff00
}
".fl-theme-lgdg": {
foregroundColor: #bdbdbb,
backgroundColor: #555555
}
}
slider-thumb-common-contrastTheme() {
background: none;
background-color: fColor;
box-shadow: none !important;
}
build-themes-prefsEditor(contrastThemes) {
for themeName in contrastThemes {
fColor = contrastThemes[themeName].foregroundColor;
bColor = contrastThemes[themeName].backgroundColor;
{themeName} {
.fl-prefsEditor {
// slider
& .fl-slider {
background: none;
background-color: fColor;
a, a:hover,
.fl-slider-handle, .fl-slider-handle:hover {
slider-thumb-common-contrastTheme();
}
}
input[type=range].fl-slider {
background: none;
background-color: fColor;
&::-webkit-slider-thumb {
slider-thumb-common-contrastTheme();
}
&::-moz-range-thumb {
slider-thumb-common-contrastTheme();
}
&::-ms-thumb {
slider-thumb-common-contrastTheme();
}
}
// ON/OFF switch
.fl-prefsEditor-switch,
.fl-prefsEditor-switch-inner,
input + label .fl-prefsEditor-switch:before {
box-shadow: none !important;
text-shadow: none;
color: inherit;
background-image: none;
background-color: inherit;
}
input:focus + label,
.fl-choice input:focus ~ label {
outline: 0.2em solid fColor;
}
}
}
.fl-prefsEditor .fl-choice label {
&{themeName},
&{themeName} span,
&{themeName} .fl-crossout {
background-color: bColor !important;
border-color: fColor !important;
color: fColor !important;
}
}
}
}
// For the separated panel
get-colors-for-separatedPanelPrefsEditor(fColor, bColor) {
background-color: bColor;
border-color: fColor;
color: fColor;
}
build-themes-separatedPanelPrefsEditor(contrastThemes) {
for themeName in contrastThemes {
fColor = contrastThemes[themeName].foregroundColor;
bColor = contrastThemes[themeName].backgroundColor;
{themeName} {
background-image: none !important;
get-colors-for-separatedPanelPrefsEditor(fColor, bColor);
.fl-prefsEditor-separatedPanel {
.fl-panelBar, .fl-prefsEditor-reset {
get-colors-for-separatedPanelPrefsEditor(fColor, bColor);
}
.fl-prefsEditor-buttons { //FLUID-4991: The shadow effect around the hide/show preferences bug fix.
box-shadow:none;
}
}
}
}
}
// For the separated panel iframe
build-themes-separatedPanelPrefsEditorFrame(contrastThemes) {
for themeName in contrastThemes {
fColor = contrastThemes[themeName].foregroundColor;
bColor = contrastThemes[themeName].backgroundColor;
.fl-prefsEditor-separatedPanel{themeName} {
background-image: none;
}
}
}
// For the general theme styling that applies to html elements and infusion components
build-themes-Enactors(contrastThemes) {
for themeName in contrastThemes {
fColor = contrastThemes[themeName].foregroundColor;
bColor = contrastThemes[themeName].backgroundColor;
{themeName} {
// General Styling
&, *, .fl-preview-theme&,
div, input,
h1, h2, h3, h4, h5, h6 {
color: fColor !important;
background-color: bColor !important;
border-top-color: fColor !important;
border-right-color: fColor !important;
border-bottom-color: fColor !important;
border-left-color: fColor !important;
}
iframe {
border-top-color: fColor !important;
border-right-color: fColor !important;
border-bottom-color: fColor !important;
border-left-color: fColor !important;
}
a {
color: fColor !important;
font-weight: bold !important;
background-color: bColor !important;
}
// Tables
th {
border-top-width: 0.1em;
border-top-style: solid;
border-top-color: fColor !important;
border-right-width: 0.1em;
border-right-style: solid;
border-right-color: fColor !important;
border-bottom-width: 0.1em;
border-bottom-style: solid;
border-bottom-color: fColor !important;
border-left-width: 0.1em;
border-left-style: solid;
border-left-color: fColor !important;
background-color: fColor !important;
color: bColor !important;
}
td {
border-top-width: 0.1em;
border-top-style: solid;
border-top-color: fColor !important;
border-right-width: 0.1em;
border-right-style: solid;
border-right-color: fColor !important;
border-bottom-width: 0.1em;
border-bottom-style: solid;
border-bottom-color: fColor !important;
border-left-width: 0.1em;
border-left-style: solid;
border-left-color: fColor !important;
}
// Focus
.fl-focus:focus, .fl-focus :focus, &.fl-focus :focus, .selectable {
outline-width: 0.2em;
outline-style: solid;
outline-color: fColor;
}
// Infusion Components
// Reorderer
.fl-reorderer-dropMarker {
background-color: #f00 !important;
}
// Inline edit
.fl-inlineEdit-edit {
background-color: fColor !important;
color: bColor !important;
border-top-width: 0.1em;
border-top-style: solid;
border-top-color: bColor !important;
border-right-width: 0.1em;
border-right-style: solid;
border-right-color: bColor !important;
border-bottom-width: 0.1em;
border-bottom-style: solid;
border-bottom-color: bColor !important;
border-left-width: 0.1em;
border-left-style: solid;
border-left-color: bColor !important;
padding-top: 0.1em;
padding-right: 0.1em;
padding-bottom: 0.1em;
padding-left: 0.1em;
margin-top: -0.1em;
margin-right: -0.1em;
margin-bottom: -0.1em;
margin-left: -0.1em;
}
// Inverted color
// General Styling
&.fl-inverted-color, &.fl-inverted-color *, .fl-preview-theme&.fl-inverted-color {
color: bColor !important;
background-color: fColor !important;
border-top-color: bColor !important;
border-right-color: bColor !important;
border-bottom-color: bColor !important;
border-left-color: bColor !important;
}
// Focus
&.fl-inverted-color.fl-focus :focus {
outline-width: 0.2em;
outline-style: solid;
outline-color: bColor;
}
.fl-inverted-color {
&, *,
div, input,
h1, h2, h3, h4, h5, h6 {
color: bColor !important;
background-color: fColor !important;
border-top-color: bColor !important;
border-right-color: bColor !important;
border-bottom-color: bColor !important;
border-left-color: bColor !important;
}
iframe {
border-top-color: bColor !important;
border-right-color: bColor !important;
border-bottom-color: bColor !important;
border-left-color: bColor !important;
}
a {
color: bColor !important;
font-weight: bold !important;
background-color: fColor !important;
}
// Tables
th {
border-top-width: 0.1em;
border-top-style: solid;
border-top-color: bColor !important;
border-right-width: 0.1em;
border-right-style: solid;
border-right-color: bColor !important;
border-bottom-width: 0.1em;
border-bottom-style: solid;
border-bottom-color: bColor !important;
border-left-width: 0.1em;
border-left-style: solid;
border-left-color: bColor !important;
background-color: bColor !important;
color: fColor !important;
}
td {
border-top-width: 0.1em;
border-top-style: solid;
border-top-color: bColor !important;
border-right-width: 0.1em;
border-right-style: solid;
border-right-color: bColor !important;
border-bottom-width: 0.1em;
border-bottom-style: solid;
border-bottom-color: bColor !important;
border-left-width: 0.1em;
border-left-style: solid;
border-left-color: bColor !important;
}
// Infusion focus element
.fl-focus:focus, .fl-focus :focus, .selectable {
outline-width: 0.2em;
outline-style: solid;
outline-color: bColor;
}
// Infusion Components
// Reorderer
.fl-reorderer-dropMarker {
background-color: #f00 !important;
}
// Inline Edit
.fl-inlineEdit-edit {
background-color: bColor !important;
color: fColor !important;
border-top-width: 0.1em;
border-top-style: solid;
border-top-color: fColor !important;
border-right-width: 0.1em;
border-right-style: solid;
border-right-color: fColor !important;
border-bottom-width: 0.1em;
border-bottom-style: solid;
border-bottom-color: fColor !important;
border-left-width: 0.1em;
border-left-style: solid;
border-left-color: fColor !important;
padding-top: 0.1em;
padding-right: 0.1em;
padding-bottom: 0.1em;
padding-left: 0.1em;
margin-top: -0.1em;
margin-right: -0.1em;
margin-bottom: -0.1em;
margin-left: -0.1em;
}
}
}
}
}

BIN
lib/infusion/src/framework/preferences/fonts/InfusionIcons-PrefsEditor.eot

Binary file not shown.

BIN
lib/infusion/src/framework/preferences/fonts/InfusionIcons-PrefsEditor.ttf

Binary file not shown.

27
lib/infusion/src/framework/preferences/html/FullNoPreviewPrefsEditor.html

@ -0,0 +1,27 @@
<form id="options" class="fl-prefsEditor fl-prefsEditor-fullNoPreview fl-focus">
<div class="fl-prefsEditors-controls">
<div class="fl-prefsEditor-category">
<h2 class="fl-force-left">Text and display</h2>
<div class="flc-prefsEditor-text-size fl-prefsEditor-text"></div>
<div class="flc-prefsEditor-text-font fl-prefsEditor-text"></div>
<div class="flc-prefsEditor-line-space fl-prefsEditor-text"></div>
<div class="flc-prefsEditor-contrast fl-prefsEditor-text"></div>
</div>
<div class="fl-prefsEditor-category">
<h2 class="fl-force-left">Layout and navigation</h2>
<div class="flc-prefsEditor-layout-controls fl-prefsEditor-layout"></div>
</div>
<div class="fl-prefsEditor-category">
<h2 class="fl-force-left">Links and buttons</h2>
<div class="flc-prefsEditor-links-controls fl-prefsEditor-links"></div>
</div>
</div>
<div class="fl-prefsEditor-buttons">
<input class="flc-prefsEditor-save" type="button" value="Save and apply" />
<input class="flc-prefsEditor-reset" type="button" value="Reset and apply" />
<input class="flc-prefsEditor-cancel" type="button" value="Cancel" />
</div>
</form>

29
lib/infusion/src/framework/preferences/html/FullPreviewPrefsEditor.html

@ -0,0 +1,29 @@
<form id="options" class="fl-prefsEditor fl-prefsEditor-fullPreview fl-focus">
<div class="fl-prefsEditor-fullPreview-adjusters fl-force-left">
<h2>Text and display</h2>
<div class="flc-prefsEditor-text-size fl-prefsEditor-category fl-prefsEditor-text"></div>
<div class="flc-prefsEditor-text-font fl-prefsEditor-category fl-prefsEditor-text"></div>
<div class="flc-prefsEditor-line-space fl-prefsEditor-category fl-prefsEditor-text"></div>
<div class="flc-prefsEditor-contrast fl-prefsEditor-category fl-prefsEditor-text"></div>
<h2>Layout and navigation</h2>
<div class="flc-prefsEditor-layout-controls fl-prefsEditor-category fl-prefsEditor-layout">
</div>
<h2>Links and buttons</h2>
<div class="flc-prefsEditor-links-controls fl-prefsEditor-category fl-prefsEditor-links"></div>
</div>
<div class="fl-prefsEditor-fullPreview-previewContainer fl-force-right">
<div class="fl-prefsEditor-preview">
<h2>Preview your changes</h2>
<iframe class="flc-prefsEditor-preview-frame"></iframe>
</div>
<div class="fl-prefsEditor-buttons">
<input class="flc-prefsEditor-save" type="button" value="Save and apply" />
<input class="flc-prefsEditor-reset" type="button" value="Reset" />
<input class="flc-prefsEditor-cancel" type="button" value="Cancel" />
</div>
</div>
</form>

13
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-contrast.html

@ -0,0 +1,13 @@
<h2><span class="fl-icon-contrast"></span><span class="flc-prefsEditor-contrast-label fl-heading-text"></span></h2>
<fieldset>
<legend class="flc-prefsEditor-contrast-descr"></legend>
<div class="flc-prefsEditor-themeRow fl-choice">
<input type="radio" class="flc-prefsEditor-themeInput fl-hidden-accessible" name="theme" id="default" value="default" />
<div class="fl-indicator"></div>
<label for="default" class="flc-prefsEditor-theme-label">
<span class="fl-preview-A"></span>
<span class="fl-hidden-accessible"></span>
<span class="fl-crossout"></span>
</label>
</div>
</fieldset>

2
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-emphasizeLinks.html

@ -0,0 +1,2 @@
<input type="checkbox" id="links-choice" class="flc-prefsEditor-links fl-force-left" />
<label class="flc-prefsEditor-links-choice-label" for="links-choice"></label>

2
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-inputsLarger.html

@ -0,0 +1,2 @@
<input type="checkbox" id="inputs-choice" class="flc-prefsEditor-inputs-larger fl-force-left" />
<label class="flc-prefsEditor-links-inputs-choice-label" for="inputs-choice"></label>

10
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-layout.html

@ -0,0 +1,10 @@
<h2><span class="fl-icon-toc"></span><span class="flc-prefsEditor-toc-label fl-heading-text"></span></h2>
<div class="fl-prefsEditor-onoff">
<input type="checkbox" id="toc-choice" class="flc-prefsEditor-toc" />
<label for="toc-choice">
<span class="flc-prefsEditor-toc-descr"></span>
<span class="fl-prefsEditor-switch" data-checkboxStateOn="ON" data-checkboxStateOff="OFF">
<span class="fl-prefsEditor-switch-inner"></span>
</span>
</label>
</div>

12
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-lineSpace-jQueryUI.html

@ -0,0 +1,12 @@
<div class="flc-prefsEditor-line-space">
<h2><span class="fl-icon-line-space"></span><label class="flc-prefsEditor-line-space-label" for="line-space"></label></h2>
<p class="flc-prefsEditor-line-space-descr"></p>
<div class="fl-inputs">
<div class="fl-force-left">
<div class="fl-icon-line-space-condensed" role="presentation"></div>
<div class="flc-textfieldSlider-slider fl-force-left fl-slider fl-slider-horz"> </div>
<div class="fl-icon-line-space-expanded" role="presentation"></div>
</div>
<div class="fl-slider-input"><input id="line-space" class="flc-textfieldSlider-field" type="text" /> <span class="flc-prefsEditor-multiplier"></span></div>
</div>
</div>

12
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-lineSpace-nativeHTML.html

@ -0,0 +1,12 @@
<div class="flc-prefsEditor-line-space">
<h2><span class="fl-icon-line-space"></span><label class="flc-prefsEditor-line-space-label" id="line-space-label"></label></h2>
<p class="flc-prefsEditor-line-space-descr"></p>
<div class="fl-inputs">
<div class="fl-force-left">
<div class="fl-icon-line-space-condensed" role="presentation"></div>
<input aria-labelledby="line-space-label" class="flc-textfieldSlider-slider fl-force-left fl-slider fl-slider-horz" />
<div class="fl-icon-line-space-expanded" role="presentation"></div>
</div>
<div class="fl-slider-input"><input aria-labelledby="line-space-label" class="flc-textfieldSlider-field" type="text" /> <span class="flc-prefsEditor-multiplier"></span></div>
</div>
</div>

5
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-linksControls.html

@ -0,0 +1,5 @@
<h2><span class="fl-icon-links"></span><span class="flc-prefsEditor-linksControls-label fl-heading-text"></span></h2>
<ul class="fl-inputs fl-links-buttons-options">
<li class="flc-prefsEditor-emphasizeLinks"></li>
<li class="flc-prefsEditor-inputsLarger"></li>
</ul>

10
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-speak.html

@ -0,0 +1,10 @@
<h2><span class="flc-prefsEditor-speak-label fl-heading-text"></span></h2>
<div class="fl-prefsEditor-onoff">
<input type="checkbox" id="speak-choice" class="flc-prefsEditor-speak" />
<label for="speak-choice">
<span class="flc-prefsEditor-speak-descr"></span>
<span class="fl-prefsEditor-switch" data-checkboxStateOn="ON" data-checkboxStateOff="OFF">
<span class="fl-prefsEditor-switch-inner"></span>
</span>
</label>
</div>

6
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-textFont.html

@ -0,0 +1,6 @@
<h2>
<span class="fl-icon-font"></span>
<label class="flc-prefsEditor-text-font-label" for="text-font"></label>
</h2>
<p class="flc-prefsEditor-text-font-descr"></p>
<select class="flc-prefsEditor-text-font fl-inputs" id="text-font"></select>

15
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-textSize-jQueryUI.html

@ -0,0 +1,15 @@
<div class="flc-prefsEditor-min-text-size">
<h2>
<span class="fl-icon-size"></span>
<label class="flc-prefsEditor-min-text-size-label" for="min-text-size"></label>
</h2>
<p class="flc-prefsEditor-text-size-descr"></p>
<div class="fl-inputs">
<div class="fl-force-left">
<div class="fl-icon-small-a" role="presentation"></div>
<div class="flc-textfieldSlider-slider fl-force-left fl-slider fl-slider-horz"> </div>
<div class="fl-icon-big-a" role="presentation"></div>
</div>
<div class="fl-slider-input"><input id="min-text-size" class="flc-textfieldSlider-field" type="text" /> <span class="flc-prefsEditor-multiplier"></span></div>
</div>
</div>

15
lib/infusion/src/framework/preferences/html/PrefsEditorTemplate-textSize-nativeHTML.html

@ -0,0 +1,15 @@
<div class="flc-prefsEditor-min-text-size">
<h2>
<span class="fl-icon-size"></span>
<label id="text-size-label" class="flc-prefsEditor-min-text-size-label"></label>
</h2>
<p class="flc-prefsEditor-text-size-descr"></p>
<div class="fl-inputs">
<div class="fl-force-left">
<div class="fl-icon-small-a" role="presentation"></div>
<input aria-labelledby="text-size-label" class="flc-textfieldSlider-slider fl-force-left fl-slider fl-slider-horz" />
<div class="fl-icon-big-a" role="presentation"></div>
</div>
<div class="fl-slider-input"><input aria-labelledby="text-size-label" class="flc-textfieldSlider-field" type="text" /> <span class="flc-prefsEditor-multiplier"></span></div>
</div>
</div>

12
lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditor.html

@ -0,0 +1,12 @@
<form id="options" class="fl-prefsEditor">
<div>
<ul class="fl-prefsEditor-panels">
<li class="fl-prefsEditor-panel"><div class="flc-prefsEditor-text-size"></div></li>
<li class="fl-prefsEditor-panel"><div class="flc-prefsEditor-text-font"></div></li>
<li class="fl-prefsEditor-panel"><div class="flc-prefsEditor-line-space"></div></li>
<li class="fl-prefsEditor-panel"><div class="flc-prefsEditor-contrast"></div></li>
<li class="fl-prefsEditor-panel"><div class="flc-prefsEditor-layout-controls"></div></li>
<li class="fl-prefsEditor-panel"><div class="flc-prefsEditor-links-controls"></div></li>
</ul>
</div>
</form>

34
lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-jQueryUI.html

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="../../../lib/normalize/css/normalize.css" />
<link rel="stylesheet" type="text/css" href="../../../framework/core/css/fluid.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-bw/bw.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-wb/wb.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-by/by.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-yb/yb.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-lgdg/lgdg.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-dglg/dglg.css" />
<!-- Component styles -->
<link rel="stylesheet" type="text/css" href="../../../framework/preferences/css/Enactors.css" />
<link rel="stylesheet" type="text/css" href="../css/PrefsEditor.css" />
<link rel="stylesheet" type="text/css" href="../css/SeparatedPanelPrefsEditorFrame.css" />
<title>Preferences Editor</title>
<script type="text/javascript" src="../../../lib/jquery/core/js/jquery.js"></script>
<script type="text/javascript" src="../../../lib/jquery/ui/js/jquery-ui.js"></script>
<script type="text/javascript" src="../../../lib/jquery/plugins/touchPunch/js/jquery.ui.touch-punch.js"></script>
</head>
<body class="fl-prefsEditor-separatedPanel fl-focus">
<div class="fl-hidden">
<div class="flc-toc-tocContainer"></div>
</div>
</body>
</html>

30
lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-nativeHTML.html

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="../../../lib/normalize/css/normalize.css" />
<link rel="stylesheet" type="text/css" href="../../../framework/core/css/fluid.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-bw/bw.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-wb/wb.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-by/by.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-yb/yb.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-lgdg/lgdg.css" />
<link rel="stylesheet" type="text/css" href="../../../lib/jquery/ui/css/fl-theme-dglg/dglg.css" />
<!-- Component styles -->
<link rel="stylesheet" type="text/css" href="../../../framework/preferences/css/Enactors.css" />
<link rel="stylesheet" type="text/css" href="../css/PrefsEditor.css" />
<link rel="stylesheet" type="text/css" href="../css/SeparatedPanelPrefsEditorFrame.css" />
<title>Preferences Editor</title>
</head>
<body class="fl-prefsEditor-separatedPanel fl-focus">
<div class="fl-hidden">
<div class="flc-toc-tocContainer"></div>
</div>
</body>
</html>

BIN
lib/infusion/src/framework/preferences/images/default/separatedpanelbg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

498
lib/infusion/src/framework/preferences/js/AuxBuilder.js

@ -0,0 +1,498 @@
/*
Copyright 2013-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.prefs");
/*******************************************************************************
* Base auxiliary schema grade
*******************************************************************************/
fluid.defaults("fluid.prefs.auxSchema", {
gradeNames: ["fluid.component"],
auxiliarySchema: {
"loaderGrades": ["fluid.prefs.separatedPanel"]
}
});
/**
* Look up the value on the given source object by using the path.
* Takes a template string containing tokens in the form of "@source-path-to-value".
* Returns a value (any type) or undefined if the path is not found.
*
* @param {object} root an object to retrieve the returned value from
* @param {String} pathRef a string that the path to the requested value is embedded into
*
* Example:
* 1. Parameters:
* source:
* {
* path1: {
* path2: "here"
* }
* }
*
* template: "@path1.path2"
*
* 2. Return: "here"
*/
fluid.prefs.expandSchemaValue = function (root, pathRef) {
if (pathRef.charAt(0) !== "@") {
return pathRef;
}
return fluid.get(root, pathRef.substring(1));
};
fluid.prefs.addAtPath = function (root, path, object) {
var existingObject = fluid.get(root, path);
fluid.set(root, path, $.extend(true, {}, existingObject, object));
return root;
};
// only works with top level elements
fluid.prefs.removeKey = function (root, key) {
var value = root[key];
delete root[key];
return value;
};
fluid.prefs.rearrangeDirect = function (root, toPath, sourcePath) {
var result = {};
var sourceValue = fluid.prefs.removeKey(root, sourcePath);
if (sourceValue) {
fluid.set(result, toPath, sourceValue);
}
return result;
};
fluid.prefs.addCommonOptions = function (root, path, commonOptions, templateValues) {
templateValues = templateValues || {};
var existingValue = fluid.get(root, path);
if (!existingValue) {
return root;
}
var opts = {}, mergePolicy = {};
fluid.each(commonOptions, function (value, key) {
// Adds "container" option only for view and renderer components
if (key === "container") {
var componentType = fluid.get(root, [path, "type"]);
var componentOptions = fluid.defaults(componentType);
// Note that this approach is not completely reliable, although it has been reviewed as "good enough" -
// a grade which modifies the creation signature of its principal type would cause numerous other problems.
// We can review this awkward kind of "anticipatory logic" when the new renderer arrives.
if (fluid.get(componentOptions, ["argumentMap", "container"]) === undefined) {
return false;
}
}
// Merge grade names defined in aux schema and system default grades
if (key.indexOf("gradeNames") !== -1) {
mergePolicy[key] = fluid.arrayConcatPolicy;
}
key = fluid.stringTemplate(key, templateValues);
value = typeof (value) === "string" ? fluid.stringTemplate(value, templateValues) : value;
fluid.set(opts, key, value);
});
fluid.set(root, path, fluid.merge(mergePolicy, existingValue, opts));
return root;
};
fluid.prefs.containerNeeded = function (root, path) {
var componentType = fluid.get(root, [path, "type"]);
var componentOptions = fluid.defaults(componentType);
return (fluid.hasGrade(componentOptions, "fluid.viewComponent") || fluid.hasGrade(componentOptions, "fluid.rendererComponent"));
};
fluid.prefs.checkPrimarySchema = function (primarySchema, prefKey) {
if (!primarySchema) {
fluid.fail("The primary schema for " + prefKey + " is not defined.");
}
return !!primarySchema;
};
fluid.prefs.expandSchemaComponents = function (auxSchema, type, prefKey, componentConfig, index, commonOptions, modelCommonOptions, mappedDefaults) {
var componentOptions = fluid.copy(componentConfig) || {};
var components = {};
var initialModel = {};
var componentName = fluid.prefs.removeKey(componentOptions, "type");
var regexp = new RegExp("\\.", "g");
var memberName = componentName.replace(regexp, "_");
var flattenedPrefKey = prefKey.replace(regexp, "_");
if (componentName) {
components[memberName] = {
type: componentName,
options: componentOptions
};
var selectors = fluid.prefs.rearrangeDirect(componentOptions, memberName, "container");
var templates = fluid.prefs.rearrangeDirect(componentOptions, memberName, "template");
var messages = fluid.prefs.rearrangeDirect(componentOptions, memberName, "message");
var preferenceMap = fluid.defaults(componentName).preferenceMap;
var map = preferenceMap[prefKey];
var prefSchema = mappedDefaults[prefKey];
fluid.each(map, function (primaryPath, internalPath) {
if (fluid.prefs.checkPrimarySchema(prefSchema, prefKey)) {
var opts = {};
if (internalPath.indexOf("model.") === 0) {
var internalModelName = internalPath.slice(6);
// Set up the binding in "rules" accepted by the modelRelay base grade of every panel
fluid.set(opts, "model", fluid.get(opts, "model") || {});
fluid.prefs.addCommonOptions(opts, "model", modelCommonOptions, {
internalModelName: internalModelName,
externalModelName: flattenedPrefKey
});
fluid.set(initialModel, ["members", "initialModel", "preferences", flattenedPrefKey], prefSchema[primaryPath]);
} else {
fluid.set(opts, internalPath, prefSchema[primaryPath]);
}
$.extend(true, componentOptions, opts);
}
});
fluid.prefs.addCommonOptions(components, memberName, commonOptions, {
prefKey: memberName
});
fluid.prefs.addAtPath(auxSchema, [type, "components"], components);
fluid.prefs.addAtPath(auxSchema, [type, "selectors"], selectors);
fluid.prefs.addAtPath(auxSchema, ["templateLoader", "resources"], templates);
fluid.prefs.addAtPath(auxSchema, ["messageLoader", "resources"], messages);
fluid.prefs.addAtPath(auxSchema, "initialModel", initialModel);
}
return auxSchema;
};
/**
* Expands a all "@" path references from an auxiliary schema.
* Note that you cannot chain "@" paths.
*
* @param {object} schemaToExpand the shcema which will be expanded
* @param {object} altSource an alternative look up object. This is primarily used for the internal recursive call.
* @return {object} an expaneded version of the schema.
*/
fluid.prefs.expandSchemaImpl = function (schemaToExpand, altSource) {
var expandedSchema = fluid.copy(schemaToExpand);
altSource = altSource || expandedSchema;
fluid.each(expandedSchema, function (value, key) {
if (typeof value === "object") {
expandedSchema[key] = fluid.prefs.expandSchemaImpl(value, altSource);
} else if (typeof value === "string") {
var expandedVal = fluid.prefs.expandSchemaValue(altSource, value);
if (expandedVal !== undefined) {
expandedSchema[key] = expandedVal;
} else {
delete expandedSchema[key];
}
}
});
return expandedSchema;
};
fluid.prefs.expandCompositePanels = function (auxSchema, compositePanelList, panelIndex, panelCommonOptions, subPanelCommonOptions,
compositePanelBasedOnSubCommonOptions, panelModelCommonOptions, mappedDefaults) {
var panelsToIgnore = [];
fluid.each(compositePanelList, function (compositeDetail, compositeKey) {
var compositePanelOptions = {};
var components = {};
var initialModel = {};
var selectors = {};
var templates = {};
var messages = {};
var selectorsToIgnore = [];
var thisCompositeOptions = fluid.copy(compositeDetail);
fluid.set(compositePanelOptions, "type", thisCompositeOptions.type);
delete thisCompositeOptions.type;
selectors = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "container");
templates = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "template");
messages = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "message");
var subPanelList = []; // list of subpanels to generate options for
var subPanels = {};
var subPanelRenderOn = {};
// thisCompositeOptions.panels can be in two forms:
// 1. an array of names of panels that should always be rendered;
// 2. an object that describes what panels should be always rendered,
// and what panels should be rendered when a preference is turned on
// The loop below is only needed for processing the latter.
if (fluid.isPlainObject(thisCompositeOptions.panels) && !fluid.isArrayable(thisCompositeOptions.panels)) {
fluid.each(thisCompositeOptions.panels, function (subpanelArray, pref) {
subPanelList = subPanelList.concat(subpanelArray);
if (pref !== "always") {
fluid.each(subpanelArray, function (onePanel) {
fluid.set(subPanelRenderOn, onePanel, pref);
});
}
});
} else {
subPanelList = thisCompositeOptions.panels;
}
fluid.each(subPanelList, function (subPanelID) {
panelsToIgnore.push(subPanelID);
var subPanelPrefsKey = fluid.get(auxSchema, [subPanelID, "type"]);
var safeSubPanelPrefsKey = fluid.prefs.subPanel.safePrefKey(subPanelPrefsKey);
selectorsToIgnore.push(safeSubPanelPrefsKey);
var subPanelOptions = fluid.copy(fluid.get(auxSchema, [subPanelID, "panel"]));
var subPanelType = fluid.get(subPanelOptions, "type");
fluid.set(subPanels, [safeSubPanelPrefsKey, "type"], subPanelType);
var renderOn = fluid.get(subPanelRenderOn, subPanelID);
if (renderOn) {
fluid.set(subPanels, [safeSubPanelPrefsKey, "options", "renderOnPreference"], renderOn);
}
// Deal with preferenceMap related options
var map = fluid.defaults(subPanelType).preferenceMap[subPanelPrefsKey];
var prefSchema = mappedDefaults[subPanelPrefsKey];
fluid.each(map, function (primaryPath, internalPath) {
if (fluid.prefs.checkPrimarySchema(prefSchema, subPanelPrefsKey)) {
var opts;
if (internalPath.indexOf("model.") === 0) {
// Set up the binding in "rules" accepted by the modelRelay base grade of every panel
fluid.set(compositePanelOptions, ["options", "model"], fluid.get(compositePanelOptions, ["options", "model"]) || {});
fluid.prefs.addCommonOptions(compositePanelOptions, ["options", "model"], panelModelCommonOptions, {
internalModelName: safeSubPanelPrefsKey,
externalModelName: safeSubPanelPrefsKey
});
fluid.set(initialModel, ["members", "initialModel", "preferences", safeSubPanelPrefsKey], prefSchema[primaryPath]);
} else {
opts = opts || {options: {}};
fluid.set(opts, "options." + internalPath, prefSchema[primaryPath]);
}
$.extend(true, subPanels[safeSubPanelPrefsKey], opts);
}
});
fluid.set(templates, safeSubPanelPrefsKey, fluid.get(subPanelOptions, "template"));
fluid.set(messages, safeSubPanelPrefsKey, fluid.get(subPanelOptions, "message"));
fluid.set(compositePanelOptions, ["options", "selectors", safeSubPanelPrefsKey], fluid.get(subPanelOptions, "container"));
fluid.set(compositePanelOptions, ["options", "resources"], fluid.get(compositePanelOptions, ["options", "resources"]) || {});
fluid.prefs.addCommonOptions(compositePanelOptions.options, "resources", compositePanelBasedOnSubCommonOptions, {
subPrefKey: safeSubPanelPrefsKey
});
// add additional options from the aux schema for subpanels
delete subPanelOptions.type;
delete subPanelOptions.template;
delete subPanelOptions.message;
delete subPanelOptions.container;
fluid.set(subPanels, [safeSubPanelPrefsKey, "options"], $.extend(true, {}, fluid.get(subPanels, [safeSubPanelPrefsKey, "options"]), subPanelOptions));
fluid.prefs.addCommonOptions(subPanels, safeSubPanelPrefsKey, subPanelCommonOptions, {
compositePanel: compositeKey,
prefKey: safeSubPanelPrefsKey
});
});
delete thisCompositeOptions.panels;
// add additional options from the aux schema for the composite panel
fluid.set(compositePanelOptions, ["options"], $.extend(true, {}, compositePanelOptions.options, thisCompositeOptions));
fluid.set(compositePanelOptions, ["options", "selectorsToIgnore"], selectorsToIgnore);
fluid.set(compositePanelOptions, ["options", "components"], subPanels);
components[compositeKey] = compositePanelOptions;
fluid.prefs.addCommonOptions(components, compositeKey, panelCommonOptions, {
prefKey: compositeKey
});
// Add onto auxSchema
fluid.prefs.addAtPath(auxSchema, ["panels", "components"], components);
fluid.prefs.addAtPath(auxSchema, ["panels", "selectors"], selectors);
fluid.prefs.addAtPath(auxSchema, ["templateLoader", "resources"], templates);
fluid.prefs.addAtPath(auxSchema, ["messageLoader", "resources"], messages);
fluid.prefs.addAtPath(auxSchema, "initialModel", initialModel);
$.extend(true, auxSchema, {panelsToIgnore: panelsToIgnore});
});
return auxSchema;
};
// Processes the auxiliary schema to output an object that contains all grade component definitions
// required for building the preferences editor, uiEnhancer and the settings store. These grade components
// are: panels, enactors, initialModel, messageLoader, templateLoader and terms.
// These grades are consumed and integrated by builder.js
// (https://github.com/fluid-project/infusion/blob/master/src/framework/preferences/js/Builder.js)
fluid.prefs.expandSchema = function (schemaToExpand, indexes, topCommonOptions, elementCommonOptions, mappedDefaults) {
var auxSchema = fluid.prefs.expandSchemaImpl(schemaToExpand);
auxSchema.namespace = auxSchema.namespace || "fluid.prefs.created_" + fluid.allocateGuid();
var terms = fluid.get(auxSchema, "terms");
if (terms) {
delete auxSchema.terms;
fluid.set(auxSchema, ["terms", "terms"], terms);
}
var compositePanelList = fluid.get(auxSchema, "groups");
if (compositePanelList) {
fluid.prefs.expandCompositePanels(auxSchema, compositePanelList, fluid.get(indexes, "panel"),
fluid.get(elementCommonOptions, "panel"), fluid.get(elementCommonOptions, "subPanel"),
fluid.get(elementCommonOptions, "compositePanelBasedOnSub"), fluid.get(elementCommonOptions, "panelModel"),
mappedDefaults);
}
fluid.each(auxSchema, function (category, prefName) {
// TODO: Replace this cumbersome scheme with one based on an extensible lookup to handlers
var type = "panel";
// Ignore the subpanels that are only for composing composite panels
if (category[type] && !fluid.contains(auxSchema.panelsToIgnore, prefName)) {
fluid.prefs.expandSchemaComponents(auxSchema, "panels", category.type, category[type], fluid.get(indexes, type),
fluid.get(elementCommonOptions, type), fluid.get(elementCommonOptions, type + "Model"), mappedDefaults);
}
type = "enactor";
if (category[type]) {
fluid.prefs.expandSchemaComponents(auxSchema, "enactors", category.type, category[type], fluid.get(indexes, type),
fluid.get(elementCommonOptions, type), fluid.get(elementCommonOptions, type + "Model"), mappedDefaults);
}
fluid.each(["template", "message"], function (type) {
if (prefName === type) {
fluid.set(auxSchema, [type + "Loader", "resources", "prefsEditor"], auxSchema[type]);
delete auxSchema[type];
}
});
});
// Remove subPanels array. It is to keep track of the panels that are only used as sub-components of composite panels.
if (auxSchema.panelsToIgnore) {
delete auxSchema.panelsToIgnore;
}
// Add top common options
fluid.each(topCommonOptions, function (topOptions, type) {
fluid.prefs.addCommonOptions(auxSchema, type, topOptions);
});
return auxSchema;
};
fluid.defaults("fluid.prefs.auxBuilder", {
gradeNames: ["fluid.prefs.auxSchema"],
mergePolicy: {
elementCommonOptions: "noexpand"
},
topCommonOptions: {
panels: {
gradeNames: ["fluid.prefs.prefsEditor"]
},
enactors: {
gradeNames: ["fluid.uiEnhancer"]
},
templateLoader: {
gradeNames: ["fluid.resourceLoader"]
},
messageLoader: {
gradeNames: ["fluid.resourceLoader"]
},
initialModel: {
gradeNames: ["fluid.prefs.initialModel"]
},
terms: {
gradeNames: ["fluid.component"]
}
},
elementCommonOptions: {
panel: {
"createOnEvent": "onPrefsEditorMarkupReady",
"container": "{prefsEditor}.dom.%prefKey",
"options.gradeNames": "fluid.prefs.prefsEditorConnections",
"options.resources.template": "{templateLoader}.resources.%prefKey",
"options.messageBase": "{messageLoader}.resources.%prefKey.resourceText"
},
panelModel: {
"%internalModelName": "{prefsEditor}.model.preferences.%externalModelName"
},
compositePanelBasedOnSub: {
"%subPrefKey": "{templateLoader}.resources.%subPrefKey"
},
subPanel: {
"container": "{%compositePanel}.dom.%prefKey",
"options.messageBase": "{messageLoader}.resources.%prefKey.resourceText"
},
enactor: {
"container": "{uiEnhancer}.container"
},
enactorModel: {
"%internalModelName": "{uiEnhancer}.model.%externalModelName"
}
},
indexes: {
panel: {
expander: {
func: "fluid.indexDefaults",
args: ["panelsIndex", {
gradeNames: "fluid.prefs.panel",
indexFunc: "fluid.prefs.auxBuilder.prefMapIndexer"
}]
}
},
enactor: {
expander: {
func: "fluid.indexDefaults",
args: ["enactorsIndex", {
gradeNames: "fluid.prefs.enactor",
indexFunc: "fluid.prefs.auxBuilder.prefMapIndexer"
}]
}
}
},
mappedDefaults: {},
expandedAuxSchema: {
expander: {
func: "fluid.prefs.expandSchema",
args: [
"{that}.options.auxiliarySchema",
"{that}.options.indexes",
"{that}.options.topCommonOptions",
"{that}.options.elementCommonOptions",
"{that}.options.mappedDefaults"
]
}
}
});
fluid.prefs.auxBuilder.prefMapIndexer = function (defaults) {
return fluid.keys(defaults.preferenceMap);
};
})(jQuery, fluid_2_0_0);

208
lib/infusion/src/framework/preferences/js/Builder.js

@ -0,0 +1,208 @@
/*
Copyright 2013-2015 OCAD University
Copyright 2015-2016 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.prefs");
fluid.defaults("fluid.prefs.builder", {
gradeNames: ["fluid.component", "fluid.prefs.auxBuilder"],
mergePolicy: {
auxSchema: "expandedAuxSchema"
},
assembledPrefsEditorGrade: {
expander: {
func: "fluid.prefs.builder.generateGrade",
args: ["prefsEditor", "{that}.options.auxSchema.namespace", {
gradeNames: ["fluid.prefs.assembler.prefsEd", "fluid.viewComponent"],
componentGrades: "{that}.options.constructedGrades",
loaderGrades: "{that}.options.auxSchema.loaderGrades"
}]
}
},
assembledUIEGrade: {
expander: {
func: "fluid.prefs.builder.generateGrade",
args: ["uie", "{that}.options.auxSchema.namespace", {
gradeNames: ["fluid.viewComponent", "fluid.prefs.assembler.uie"],
componentGrades: "{that}.options.constructedGrades"
}]
}
},
constructedGrades: {
expander: {
func: "fluid.prefs.builder.constructGrades",
args: ["{that}.options.auxSchema", ["enactors", "messages", "panels", "initialModel", "templateLoader", "messageLoader", "terms"]]
}
},
mappedDefaults: "{primaryBuilder}.options.schema.properties",
components: {
primaryBuilder: {
type: "fluid.prefs.primaryBuilder",
options: {
typeFilter: {
expander: {
func: "fluid.prefs.builder.parseAuxSchema",
args: "{builder}.options.auxiliarySchema"
}
}
}
}
},
distributeOptions: [{
source: "{that}.options.primarySchema",
removeSource: true,
target: "{that > primaryBuilder}.options.primarySchema"
}]
});
fluid.defaults("fluid.prefs.assembler.uie", {
gradeNames: ["fluid.viewComponent"],
components: {
// These two components become global
store: {
type: "fluid.prefs.globalSettingsStore",
options: {
distributeOptions: {
target: "{that fluid.prefs.store}.options.contextAwareness.strategy.checks.user",
record: {
contextValue: "{fluid.prefs.assembler.uie}.options.storeType",
gradeNames: "{fluid.prefs.assembler.uie}.options.storeType"
}
}
}
},
enhancer: {
type: "fluid.component",
options: {
gradeNames: "{that}.options.enhancerType",
enhancerType: "fluid.pageEnhancer",
components: {
uiEnhancer: {
options: {
gradeNames: ["{fluid.prefs.assembler.uie}.options.componentGrades.enactors"]
}
}
}
}
}
},
distributeOptions: [{
source: "{that}.options.enhancer",
target: "{that uiEnhancer}.options",
removeSource: true
}, { // TODO: not clear that this hits anything since settings store is not a subcomponent
source: "{that}.options.store",
target: "{that fluid.prefs.store}.options"
}, {
source: "{that}.options.enhancerType",
target: "{that > enhancer}.options.enhancerType"
}]
});
fluid.defaults("fluid.prefs.assembler.prefsEd", {
gradeNames: ["fluid.viewComponent", "fluid.prefs.assembler.uie"],
components: {
prefsEditorLoader: {
type: "fluid.viewComponent",
container: "{fluid.prefs.assembler.prefsEd}.container",
priority: "last",
options: {
gradeNames: [
"{fluid.prefs.assembler.prefsEd}.options.componentGrades.terms",
"{fluid.prefs.assembler.prefsEd}.options.componentGrades.messages",
"{fluid.prefs.assembler.prefsEd}.options.componentGrades.initialModel",
"{that}.options.loaderGrades"
],
templateLoader: {
gradeNames: ["{fluid.prefs.assembler.prefsEd}.options.componentGrades.templateLoader"]
},
messageLoader: {
gradeNames: ["{fluid.prefs.assembler.prefsEd}.options.componentGrades.messageLoader"]
},
prefsEditor: {
gradeNames: ["{fluid.prefs.assembler.prefsEd}.options.componentGrades.panels", "fluid.prefs.uiEnhancerRelay"]
},
events: {
onReady: "{fluid.prefs.assembler.prefsEd}.events.onPrefsEditorReady"
}
}
}
},
events: {
onPrefsEditorReady: null,
onReady: {
events: {
onPrefsEditorReady: "onPrefsEditorReady",
onCreate: "onCreate"
},
args: ["{that}"]
}
},
distributeOptions: [{
source: "{that}.options.loaderGrades",
removeSource: true,
target: "{that > prefsEditorLoader}.options.loaderGrades"
}, {
source: "{that}.options.prefsEditor",
removeSource: true,
target: "{that prefsEditor}.options"
}, {
source: "{that}.options.terms",
removeSource: true,
target: "{that prefsEditorLoader}.options.terms"
}]
});
fluid.prefs.builder.generateGrade = function (name, namespace, options) {
var gradeNameTemplate = "%namespace.%name";
var gradeName = fluid.stringTemplate(gradeNameTemplate, {name: name, namespace: namespace});
fluid.defaults(gradeName, options);
return gradeName;
};
fluid.prefs.builder.constructGrades = function (auxSchema, gradeCategories) {
var constructedGrades = {};
fluid.each(gradeCategories, function (category) {
var gradeOpts = auxSchema[category];
if (fluid.get(gradeOpts, "gradeNames")) {
constructedGrades[category] = fluid.prefs.builder.generateGrade(category, auxSchema.namespace, gradeOpts);
}
});
return constructedGrades;
};
fluid.prefs.builder.parseAuxSchema = function (auxSchema) {
var auxTypes = [];
fluid.each(auxSchema, function parse(field) {
var type = field.type;
if (type) {
auxTypes.push(type);
}
});
return auxTypes;
};
/*
* A one-stop-shop function to build and instantiate a prefsEditor from a schema.
*/
fluid.prefs.create = function (container, options) {
options = options || {};
var builder = fluid.prefs.builder(options.build);
return fluid.invokeGlobalFunction(builder.options.assembledPrefsEditorGrade, [container, options.prefsEditor]);
};
})(jQuery, fluid_2_0_0);

431
lib/infusion/src/framework/preferences/js/Enactors.js

@ -0,0 +1,431 @@
/*
Copyright 2013-2016 OCAD University
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.defaults("fluid.prefs.enactor", {
gradeNames: ["fluid.modelComponent"]
});
/**********************************************************************************
* styleElements
*
* Adds or removes the classname to/from the elements based upon the model value.
* This component is used as a grade by emphasizeLinks & inputsLarger
**********************************************************************************/
fluid.defaults("fluid.prefs.enactor.styleElements", {
gradeNames: ["fluid.prefs.enactor"],
cssClass: null, // Must be supplied by implementors
elementsToStyle: null, // Must be supplied by implementors
invokers: {
applyStyle: {
funcName: "fluid.prefs.enactor.styleElements.applyStyle",
args: ["{arguments}.0", "{arguments}.1"]
},
resetStyle: {
funcName: "fluid.prefs.enactor.styleElements.resetStyle",
args: ["{arguments}.0", "{arguments}.1"]
},
handleStyle: {
funcName: "fluid.prefs.enactor.styleElements.handleStyle",
args: ["{arguments}.0", "{that}.options.elementsToStyle", "{that}.options.cssClass", "{that}.applyStyle", "{that}.resetStyle"]
}
},
modelListeners: {
value: {
listener: "{that}.handleStyle",
args: ["{change}.value"]
}
}
});
fluid.prefs.enactor.styleElements.applyStyle = function (elements, cssClass) {
elements.addClass(cssClass);
};
fluid.prefs.enactor.styleElements.resetStyle = function (elements, cssClass) {
$(elements, "." + cssClass).addBack().removeClass(cssClass);
};
fluid.prefs.enactor.styleElements.handleStyle = function (value, elements, cssClass, applyStyleFunc, resetStyleFunc) {
var func = value ? applyStyleFunc : resetStyleFunc;
func(elements, cssClass);
};
/*******************************************************************************
* ClassSwapper
*
* Has a hash of classes it cares about and will remove all those classes from
* its container before setting the new class.
* This component tends to be used as a grade by textFont and contrast
*******************************************************************************/
fluid.defaults("fluid.prefs.enactor.classSwapper", {
gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
classes: {}, // Must be supplied by implementors
invokers: {
clearClasses: {
funcName: "fluid.prefs.enactor.classSwapper.clearClasses",
args: ["{that}.container", "{that}.classStr"]
},
swap: {
funcName: "fluid.prefs.enactor.classSwapper.swap",
args: ["{arguments}.0", "{that}", "{that}.clearClasses"]
}
},
modelListeners: {
value: {
listener: "{that}.swap",
args: ["{change}.value"]
}
},
members: {
classStr: {
expander: {
func: "fluid.prefs.enactor.classSwapper.joinClassStr",
args: "{that}.options.classes"
}
}
}
});
fluid.prefs.enactor.classSwapper.clearClasses = function (container, classStr) {
container.removeClass(classStr);
};
fluid.prefs.enactor.classSwapper.swap = function (value, that, clearClassesFunc) {
clearClassesFunc();
that.container.addClass(that.options.classes[value]);
};
fluid.prefs.enactor.classSwapper.joinClassStr = function (classes) {
var classStr = "";
fluid.each(classes, function (oneClassName) {
if (oneClassName) {
classStr += classStr ? " " + oneClassName : oneClassName;
}
});
return classStr;
};
/*******************************************************************************
* emphasizeLinks
*
* The enactor to emphasize links in the container according to the value
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.emphasizeLinks", {
gradeNames: ["fluid.prefs.enactor.styleElements", "fluid.viewComponent"],
preferenceMap: {
"fluid.prefs.emphasizeLinks": {
"model.value": "default"
}
},
cssClass: null, // Must be supplied by implementors
elementsToStyle: "{that}.container"
});
/*******************************************************************************
* inputsLarger
*
* The enactor to enlarge inputs in the container according to the value
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.inputsLarger", {
gradeNames: ["fluid.prefs.enactor.styleElements", "fluid.viewComponent"],
preferenceMap: {
"fluid.prefs.inputsLarger": {
"model.value": "default"
}
},
cssClass: null, // Must be supplied by implementors
elementsToStyle: "{that}.container"
});
/*******************************************************************************
* textFont
*
* The enactor to change the font face used according to the value
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.textFont", {
gradeNames: ["fluid.prefs.enactor.classSwapper"],
preferenceMap: {
"fluid.prefs.textFont": {
"model.value": "default"
}
}
});
/*******************************************************************************
* contrast
*
* The enactor to change the contrast theme according to the value
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.contrast", {
gradeNames: ["fluid.prefs.enactor.classSwapper"],
preferenceMap: {
"fluid.prefs.contrast": {
"model.value": "default"
}
}
});
/*******************************************************************************
* Functions shared by textSize and lineSpace
*******************************************************************************/
/**
* return "font-size" in px
* @param (Object) container
* @param (Object) fontSizeMap: the mapping between the font size string values ("small", "medium" etc) to px values
*/
fluid.prefs.enactor.getTextSizeInPx = function (container, fontSizeMap) {
var fontSize = container.css("font-size");
if (fontSizeMap[fontSize]) {
fontSize = fontSizeMap[fontSize];
}
// return fontSize in px
return parseFloat(fontSize);
};
/*******************************************************************************
* textSize
*
* Sets the text size on the root element to the multiple provided.
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.textSize", {
gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
preferenceMap: {
"fluid.prefs.textSize": {
"model.value": "default"
}
},
members: {
root: {
expander: {
"this": "{that}.container",
"method": "closest", // ensure that the correct document is being used. i.e. in an iframe
"args": ["html"]
}
}
},
fontSizeMap: {}, // must be supplied by implementors
invokers: {
set: {
funcName: "fluid.prefs.enactor.textSize.set",
args: ["{arguments}.0", "{that}", "{that}.getTextSizeInPx"]
},
getTextSizeInPx: {
funcName: "fluid.prefs.enactor.getTextSizeInPx",
args: ["{that}.root", "{that}.options.fontSizeMap"]
}
},
modelListeners: {
value: {
listener: "{that}.set",
args: ["{change}.value"]
}
}
});
fluid.prefs.enactor.textSize.set = function (times, that, getTextSizeInPxFunc) {
times = times || 1;
// Calculating the initial size here rather than using a members expand because the "font-size"
// cannot be detected on hidden containers such as separated paenl iframe.
if (!that.initialSize) {
that.initialSize = getTextSizeInPxFunc();
}
if (that.initialSize) {
var targetSize = times * that.initialSize;
that.root.css("font-size", targetSize + "px");
}
};
/*******************************************************************************
* lineSpace
*
* Sets the line space on the container to the multiple provided.
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.lineSpace", {
gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
preferenceMap: {
"fluid.prefs.lineSpace": {
"model.value": "default"
}
},
fontSizeMap: {}, // must be supplied by implementors
invokers: {
set: {
funcName: "fluid.prefs.enactor.lineSpace.set",
args: ["{arguments}.0", "{that}", "{that}.getLineHeightMultiplier"]
},
getTextSizeInPx: {
funcName: "fluid.prefs.enactor.getTextSizeInPx",
args: ["{that}.container", "{that}.options.fontSizeMap"]
},
getLineHeight: {
funcName: "fluid.prefs.enactor.lineSpace.getLineHeight",
args: "{that}.container"
},
getLineHeightMultiplier: {
funcName: "fluid.prefs.enactor.lineSpace.getLineHeightMultiplier",
args: [{expander: {func: "{that}.getLineHeight"}}, {expander: {func: "{that}.getTextSizeInPx"}}]
}
},
modelListeners: {
value: {
listener: "{that}.set",
args: ["{change}.value"]
}
}
});
// Get the line-height of an element
// In IE8 and IE9 this will return the line-height multiplier
// In other browsers it will return the pixel value of the line height.
fluid.prefs.enactor.lineSpace.getLineHeight = function (container) {
return container.css("line-height");
};
// Interprets browser returned "line-height" value, either a string "normal", a number with "px" suffix or "undefined"
// into a numeric value in em.
// Return 0 when the given "lineHeight" argument is "undefined" (http://issues.fluidproject.org/browse/FLUID-4500).
fluid.prefs.enactor.lineSpace.getLineHeightMultiplier = function (lineHeight, fontSize) {
// Handle the given "lineHeight" argument is "undefined", which occurs when firefox detects
// "line-height" css value on a hidden container. (http://issues.fluidproject.org/browse/FLUID-4500)
if (!lineHeight) {
return 0;
}
// Needs a better solution. For now, "line-height" value "normal" is defaulted to 1.2em
// according to https://developer.mozilla.org/en/CSS/line-height
if (lineHeight === "normal") {
return 1.2;
}
// Continuing the work-around of jQuery + IE bug - http://bugs.jquery.com/ticket/2671
if (lineHeight.match(/[0-9]$/)) {
return Number(lineHeight);
}
return Math.round(parseFloat(lineHeight) / fontSize * 100) / 100;
};
fluid.prefs.enactor.lineSpace.set = function (times, that, getLineHeightMultiplierFunc) {
// Calculating the initial size here rather than using a members expand because the "line-height"
// cannot be detected on hidden containers such as separated paenl iframe.
if (!that.initialSize) {
that.initialSize = getLineHeightMultiplierFunc();
}
// that.initialSize === 0 when the browser returned "lineHeight" css value is undefined,
// which occurs when firefox detects "line-height" value on a hidden container.
// @ See getLineHeightMultiplier() & http://issues.fluidproject.org/browse/FLUID-4500
if (that.initialSize) {
var targetLineSpace = times * that.initialSize;
that.container.css("line-height", targetLineSpace);
}
};
/*******************************************************************************
* tableOfContents
*
* To create and show/hide table of contents
*******************************************************************************/
// Note that the implementors need to provide the container for this view component
fluid.defaults("fluid.prefs.enactor.tableOfContents", {
gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
preferenceMap: {
"fluid.prefs.tableOfContents": {
"model.toc": "default"
}
},
tocTemplate: null, // must be supplied by implementors
components: {
tableOfContents: {
type: "fluid.tableOfContents",
container: "{fluid.prefs.enactor.tableOfContents}.container",
createOnEvent: "onCreateTOCReady",
options: {
components: {
levels: {
type: "fluid.tableOfContents.levels",
options: {
resources: {
template: {
forceCache: true,
url: "{fluid.prefs.enactor.tableOfContents}.options.tocTemplate"
}
}
}
}
},
listeners: {
"afterRender.boilAfterTocRender": "{fluid.prefs.enactor.tableOfContents}.events.afterTocRender"
}
}
}
},
invokers: {
applyToc: {
funcName: "fluid.prefs.enactor.tableOfContents.applyToc",
args: ["{arguments}.0", "{that}"]
}
},
events: {
onCreateTOCReady: null,
afterTocRender: null,
onLateRefreshRelay: null
},
modelListeners: {
toc: {
listener: "{that}.applyToc",
args: ["{change}.value"]
}
},
distributeOptions: {
source: "{that}.options.ignoreForToC",
target: "{that tableOfContents}.options.ignoreForToC"
}
});
fluid.prefs.enactor.tableOfContents.applyToc = function (value, that) {
if (value) {
if (that.tableOfContents) {
that.tableOfContents.show();
} else {
that.events.onCreateTOCReady.fire();
}
} else if (that.tableOfContents) {
that.tableOfContents.hide();
}
};
})(jQuery, fluid_2_0_0);

49
lib/infusion/src/framework/preferences/js/FullNoPreviewPrefsEditor.js

@ -0,0 +1,49 @@
/*
Copyright 2011-2016 OCAD University
Copyright 2011 Lucendo Development Ltd.
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/**************************************
* Full No Preview Preferences Editor *
**************************************/
fluid.defaults("fluid.prefs.fullNoPreview", {
gradeNames: ["fluid.prefs.prefsEditorLoader"],
components: {
prefsEditor: {
container: "{that}.container",
options: {
listeners: {
"afterReset.applyChanges": {
listener: "{that}.applyChanges"
},
"afterReset.save": {
listener: "{that}.save",
priority: "after:applyChanges"
},
"onReady.boilOnReady": {
listener: "{fullNoPreview}.events.onReady",
args: "{fullNoPreview}"
}
}
}
}
},
events: {
onReady: null
}
});
})(jQuery, fluid_2_0_0);

80
lib/infusion/src/framework/preferences/js/FullPreviewPrefsEditor.js

@ -0,0 +1,80 @@
/*
Copyright 2011-2016 OCAD University
Copyright 2011 Lucendo Development Ltd.
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/***********************************
* Full Preview Preferences Editor *
***********************************/
fluid.defaults("fluid.prefs.fullPreview", {
gradeNames: ["fluid.prefs.prefsEditorLoader"],
outerUiEnhancerOptions: "{originalEnhancerOptions}.options.originalUserOptions",
outerUiEnhancerGrades: "{originalEnhancerOptions}.uiEnhancer.options.userGrades",
components: {
prefsEditor: {
container: "{that}.container",
options: {
components: {
preview: {
type: "fluid.prefs.preview",
createOnEvent: "onReady",
container: "{prefsEditor}.dom.previewFrame",
options: {
listeners: {
"onReady.boilOnPreviewReady": "{fullPreview}.events.onPreviewReady"
}
}
}
},
listeners: {
"onReady.boilOnPrefsEditorReady": "{fullPreview}.events.onPrefsEditorReady"
},
distributeOptions: {
source: "{that}.options.preview",
removeSource: true,
target: "{that > preview}.options"
}
}
}
},
events: {
onPrefsEditorReady: null,
onPreviewReady: null,
onReady: {
events: {
onPrefsEditorReady: "onPrefsEditorReady",
onPreviewReady: "onPreviewReady"
},
args: "{that}"
}
},
distributeOptions: [{
source: "{that}.options.outerUiEnhancerOptions",
target: "{that enhancer}.options"
}, {
source: "{that}.options.preview",
target: "{that preview}.options"
}, {
source: "{that}.options.previewEnhancer",
target: "{that enhancer}.options"
}, {
source: "{that}.options.outerUiEnhancerGrades",
target: "{that enhancer}.options.gradeNames"
}]
});
})(jQuery, fluid_2_0_0);

1034
lib/infusion/src/framework/preferences/js/Panels.js

File diff suppressed because it is too large Load Diff

473
lib/infusion/src/framework/preferences/js/PrefsEditor.js

@ -0,0 +1,473 @@
/*
Copyright 2009 University of Toronto
Copyright 2010-2016 OCAD University
Copyright 2011 Lucendo Development Ltd.
Copyright 2012-2014 Raising the Floor - US
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/*****************************
* Preferences Editor Loader *
*****************************/
/**
* An Preferences Editor top-level component that reflects the collaboration between prefsEditor, templateLoader and messageLoader.
* This component is the only Preferences Editor component that is intended to be called by the outside world.
*
* @param {Object} options
*/
fluid.defaults("fluid.prefs.prefsEditorLoader", {
gradeNames: ["fluid.prefs.settingsGetter", "fluid.prefs.initialModel", "fluid.viewComponent"],
defaultLocale: "en",
members: {
settings: {
expander: {
funcName: "fluid.prefs.prefsEditorLoader.getCompleteSettings",
args: ["{that}.initialModel", "{that}.getSettings"]
}
}
},
components: {
prefsEditor: {
priority: "last",
type: "fluid.prefs.prefsEditor",
createOnEvent: "onCreatePrefsEditorReady",
options: {
members: {
initialModel: "{prefsEditorLoader}.initialModel"
},
invokers: {
getSettings: "{prefsEditorLoader}.getSettings"
}
}
},
templateLoader: {
type: "fluid.resourceLoader",
options: {
events: {
onResourcesLoaded: "{prefsEditorLoader}.events.onPrefsEditorTemplatesLoaded"
}
}
},
messageLoader: {
type: "fluid.resourceLoader",
options: {
defaultLocale: "{prefsEditorLoader}.options.defaultLocale",
locale: "{prefsEditorLoader}.settings.locale",
resourceOptions: {
dataType: "json"
},
events: {
onResourcesLoaded: "{prefsEditorLoader}.events.onPrefsEditorMessagesLoaded"
}
}
}
},
events: {
onPrefsEditorTemplatesLoaded: null,
onPrefsEditorMessagesLoaded: null,
onCreatePrefsEditorReady: {
events: {
templateLoaded: "onPrefsEditorTemplatesLoaded",
prefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded"
}
}
},
distributeOptions: [{
source: "{that}.options.templateLoader",
removeSource: true,
target: "{that > templateLoader}.options"
}, {
source: "{that}.options.messageLoader",
removeSource: true,
target: "{that > messageLoader}.options"
}, {
source: "{that}.options.terms",
target: "{that > templateLoader}.options.terms"
}, {
source: "{that}.options.terms",
target: "{that > messageLoader}.options.terms"
}, {
source: "{that}.options.prefsEditor",
removeSource: true,
target: "{that > prefsEditor}.options"
}]
});
fluid.prefs.prefsEditorLoader.getCompleteSettings = function (initialModel, getSettingsFunc) {
var savedSettings = getSettingsFunc();
return $.extend(true, {}, initialModel, savedSettings);
};
// TODO: This mixin grade appears to be supplied manually by various test cases but no longer appears in
// the main configuration. We should remove the need for users to supply this - also the use of "defaultPanels" in fact
// refers to "starter panels"
fluid.defaults("fluid.prefs.transformDefaultPanelsOptions", {
// Do not supply "fluid.prefs.inline" here, since when this is used as a mixin for separatedPanel, it ends up displacing the
// more refined type of the prefsEditorLoader
gradeNames: ["fluid.viewComponent"],
distributeOptions: [{
source: "{that}.options.textSize",
removeSource: true,
target: "{that textSize}.options"
}, {
source: "{that}.options.lineSpace",
removeSource: true,
target: "{that lineSpace}.options"
}, {
source: "{that}.options.textFont",
removeSource: true,
target: "{that textFont}.options"
}, {
source: "{that}.options.contrast",
removeSource: true,
target: "{that contrast}.options"
}, {
source: "{that}.options.layoutControls",
removeSource: true,
target: "{that layoutControls}.options"
}, {
source: "{that}.options.linksControls",
removeSource: true,
target: "{that linksControls}.options"
}]
});
/**********************
* Preferences Editor *
**********************/
fluid.defaults("fluid.prefs.settingsGetter", {
gradeNames: ["fluid.component"],
members: {
getSettings: "{fluid.prefs.store}.get"
}
});
fluid.defaults("fluid.prefs.settingsSetter", {
gradeNames: ["fluid.component"],
invokers: {
setSettings: {
funcName: "fluid.prefs.settingsSetter.setSettings",
args: ["{arguments}.0", "{fluid.prefs.store}.set"]
}
}
});
fluid.prefs.settingsSetter.setSettings = function (model, set) {
var userSettings = fluid.copy(model);
set(userSettings);
};
fluid.defaults("fluid.prefs.uiEnhancerRelay", {
gradeNames: ["fluid.modelComponent"],
listeners: {
"onCreate.addListener": "{that}.addListener",
"onDestroy.removeListener": "{that}.removeListener"
},
events: {
updateEnhancerModel: "{fluid.prefs.prefsEditor}.events.onUpdateEnhancerModel"
},
invokers: {
addListener: {
funcName: "fluid.prefs.uiEnhancerRelay.addListener",
args: ["{that}.events.updateEnhancerModel", "{that}.updateEnhancerModel"]
},
removeListener: {
funcName: "fluid.prefs.uiEnhancerRelay.removeListener",
args: ["{that}.events.updateEnhancerModel", "{that}.updateEnhancerModel"]
},
updateEnhancerModel: {
funcName: "fluid.prefs.uiEnhancerRelay.updateEnhancerModel",
args: ["{uiEnhancer}", "{fluid.prefs.prefsEditor}.model.preferences"]
}
}
});
fluid.prefs.uiEnhancerRelay.addListener = function (modelChanged, listener) {
modelChanged.addListener(listener);
};
fluid.prefs.uiEnhancerRelay.removeListener = function (modelChanged, listener) {
modelChanged.removeListener(listener);
};
fluid.prefs.uiEnhancerRelay.updateEnhancerModel = function (uiEnhancer, newModel) {
uiEnhancer.updateModel(newModel);
};
/**
* A component that works in conjunction with the UI Enhancer component
* to allow users to set personal user interface preferences. The Preferences Editor component provides a user
* interface for setting and saving personal preferences, and the UI Enhancer component carries out the
* work of applying those preferences to the user interface.
*
* @param {Object} container
* @param {Object} options
*/
fluid.defaults("fluid.prefs.prefsEditor", {
gradeNames: ["fluid.prefs.settingsGetter", "fluid.prefs.settingsSetter", "fluid.prefs.initialModel", "fluid.viewComponent"],
invokers: {
/**
* Updates the change applier and fires modelChanged on subcomponent fluid.prefs.controls
*
* @param {Object} newModel
* @param {Object} source
*/
fetch: {
funcName: "fluid.prefs.prefsEditor.fetch",
args: ["{that}", "{arguments}.0"]
},
applyChanges: {
funcName: "fluid.prefs.prefsEditor.applyChanges",
args: ["{that}"]
},
save: {
funcName: "fluid.prefs.prefsEditor.save",
args: ["{that}"]
},
saveAndApply: {
funcName: "fluid.prefs.prefsEditor.saveAndApply",
args: ["{that}"]
},
reset: {
funcName: "fluid.prefs.prefsEditor.reset",
args: ["{that}"]
},
cancel: {
funcName: "fluid.prefs.prefsEditor.cancel",
args: ["{that}"]
}
},
selectors: {
cancel: ".flc-prefsEditor-cancel",
reset: ".flc-prefsEditor-reset",
save: ".flc-prefsEditor-save",
previewFrame : ".flc-prefsEditor-preview-frame"
},
events: {
onSave: null,
onCancel: null,
beforeReset: null,
afterReset: null,
onAutoSave: null,
modelChanged: null,
onPrefsEditorRefresh: null,
onUpdateEnhancerModel: null,
onPrefsEditorMarkupReady: null,
onReady: null
},
listeners: {
"onCreate.init": "fluid.prefs.prefsEditor.init",
"onAutoSave.save": "{that}.save"
},
modelListeners: {
"": [{
listener: "fluid.prefs.prefsEditor.handleAutoSave",
args: ["{that}"]
}, {
listener: "{that}.events.modelChanged.fire",
args: ["{change}.value"]
}]
},
resources: {
template: "{templateLoader}.resources.prefsEditor"
},
autoSave: false
});
/**
* Refresh PrefsEditor
*/
fluid.prefs.prefsEditor.applyChanges = function (that) {
that.events.onUpdateEnhancerModel.fire();
};
fluid.prefs.prefsEditor.fetch = function (that, eventName) {
var completeModel = that.getSettings();
completeModel = $.extend(true, {}, that.initialModel, completeModel);
// TODO: This may not be completely effective if the root model is smaller than
// the current one. Given our previous discoveries re "model shrinkage"
// (http://issues.fluidproject.org/browse/FLUID-5585 ), the proper thing to do here
// is to apply a DELETE to the root before putting in the new model. And this should
// be done within a transaction in order to avoid notifying the tree more than necessary.
// However, the transactional model of the changeApplier is going to change radically
// soon (http://wiki.fluidproject.org/display/fluid/New+New+Notes+on+the+ChangeApplier)
// and this implementation doesn't seem to be causing a problem at present so we had
// just better leave it the way it is for now.
that.applier.change("", completeModel);
if (eventName) {
that.events[eventName].fire(that);
}
that.events.onPrefsEditorRefresh.fire();
that.applyChanges();
};
/**
* Sends the prefsEditor.model to the store and fires onSave
* @param that: A fluid.prefs.prefsEditor instance
* @return the saved model
*/
fluid.prefs.prefsEditor.save = function (that) {
if (!that.model) { // Don't save a reset model
return;
}
var modelToSave = fluid.copy(that.model),
initialModel = that.initialModel,
stats = {changes: 0, unchanged: 0, changeMap: {}},
changedPrefs = {};
// To address https://issues.fluidproject.org/browse/FLUID-4686
fluid.model.diff(modelToSave.preferences, fluid.get(initialModel, ["preferences"]), stats);
if (stats.changes === 0) {
delete modelToSave.preferences;
} else {
fluid.each(stats.changeMap, function (state, pref) {
fluid.set(changedPrefs, pref, modelToSave.preferences[pref]);
});
modelToSave.preferences = changedPrefs;
}
that.events.onSave.fire(modelToSave);
that.setSettings(modelToSave);
return modelToSave;
};
fluid.prefs.prefsEditor.saveAndApply = function (that) {
var prevSettings = that.getSettings(),
changedSelections = that.save();
// Only when preferences are changed, re-render panels and trigger enactors to apply changes
if (!fluid.model.diff(fluid.get(changedSelections, "preferences"), fluid.get(prevSettings, "preferences"))) {
that.events.onPrefsEditorRefresh.fire();
that.applyChanges();
}
};
/**
* Resets the selections to the integrator's defaults and fires afterReset
*/
fluid.prefs.prefsEditor.reset = function (that) {
that.events.beforeReset.fire(that);
that.applier.fireChangeRequest({path: "", type: "DELETE"});
that.applier.change("", fluid.copy(that.initialModel));
that.events.onPrefsEditorRefresh.fire();
that.events.afterReset.fire(that);
};
/**
* Resets the selections to the last saved selections and fires onCancel
*/
fluid.prefs.prefsEditor.cancel = function (that) {
that.events.onCancel.fire();
that.fetch();
};
// called once markup is applied to the document containing tab component roots
fluid.prefs.prefsEditor.finishInit = function (that) {
var bindHandlers = function (that) {
var saveButton = that.locate("save");
if (saveButton.length > 0) {
saveButton.click(that.saveAndApply);
var form = fluid.findForm(saveButton);
$(form).submit(function () {
that.saveAndApply();
});
}
that.locate("reset").click(that.reset);
that.locate("cancel").click(that.cancel);
};
that.container.append(that.options.resources.template.resourceText);
bindHandlers(that);
that.fetch("onPrefsEditorMarkupReady");
that.events.onReady.fire(that);
};
fluid.prefs.prefsEditor.handleAutoSave = function (that) {
if (that.options.autoSave) {
that.events.onAutoSave.fire();
}
};
fluid.prefs.prefsEditor.init = function (that) {
// This setTimeout is to ensure that fetching of resources is asynchronous,
// and so that component construction does not run ahead of subcomponents for SeparatedPanel
// (FLUID-4453 - this may be a replacement for a branch removed for a FLUID-2248 fix)
setTimeout(function () {
if (!fluid.isDestroyed(that)) {
fluid.prefs.prefsEditor.finishInit(that);
}
}, 1);
};
/******************************
* Preferences Editor Preview *
******************************/
fluid.defaults("fluid.prefs.preview", {
gradeNames: ["fluid.viewComponent"],
components: {
enhancer: {
type: "fluid.uiEnhancer",
container: "{preview}.enhancerContainer",
createOnEvent: "onReady"
},
templateLoader: "{templateLoader}"
},
invokers: {
updateModel: {
funcName: "fluid.prefs.preview.updateModel",
args: [
"{preview}",
"{prefsEditor}.model.preferences"
]
}
},
events: {
onReady: null
},
listeners: {
"onCreate.startLoadingContainer": "fluid.prefs.preview.startLoadingContainer",
"{prefsEditor}.events.modelChanged": "{that}.updateModel",
"onReady.updateModel": "{that}.updateModel"
},
templateUrl: "%prefix/PrefsEditorPreview.html"
});
fluid.prefs.preview.updateModel = function (that, preferences) {
/**
* SetTimeout is temp fix for http://issues.fluidproject.org/browse/FLUID-2248
*/
setTimeout(function () {
if (that.enhancer) {
that.enhancer.updateModel(preferences);
}
}, 0);
};
fluid.prefs.preview.startLoadingContainer = function (that) {
var templateUrl = that.templateLoader.transformURL(that.options.templateUrl);
that.container.on("load", function () {
that.enhancerContainer = $("body", that.container.contents());
that.events.onReady.fire();
});
that.container.attr("src", templateUrl);
};
})(jQuery, fluid_2_0_0);

129
lib/infusion/src/framework/preferences/js/PrimaryBuilder.js

@ -0,0 +1,129 @@
/*
Copyright 2013 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.prefs.schemas");
/**
* A custom merge policy that merges primary schema blocks and
* places them in the right location (consistent with the JSON schema
* format).
* @param {JSON} target A base for merging the options.
* @param {JSON} source Options being merged.
* @return {JSON} Updated target.
*/
fluid.prefs.schemas.merge = function (target, source) {
if (!target) {
target = {
type: "object",
properties: {}
};
}
// We can handle both schema blocks in options directly and also inside
// the |properties| field.
source = source.properties || source;
$.extend(true, target.properties, source);
return target;
};
/*******************************************************************************
* Primary builder grade
*******************************************************************************/
fluid.defaults("fluid.prefs.primaryBuilder", {
gradeNames: ["fluid.component", "{that}.buildPrimary"],
// An index of all schema grades registered with the framework.
schemaIndex: {
expander: {
func: "fluid.indexDefaults",
args: ["schemaIndex", {
gradeNames: "fluid.prefs.schemas",
indexFunc: "fluid.prefs.primaryBuilder.defaultSchemaIndexer"
}]
}
},
primarySchema: {},
// A list of all necessarry top level preference names.
typeFilter: [],
invokers: {
// An invoker used to generate a set of grades that comprise a
// final version of the primary schema to be used by the PrefsEditor
// builder.
buildPrimary: {
funcName: "fluid.prefs.primaryBuilder.buildPrimary",
args: [
"{that}.options.schemaIndex",
"{that}.options.typeFilter",
"{that}.options.primarySchema"
]
}
}
});
/**
* An invoker method that builds a list of grades that comprise a final
* version of the primary schema.
* @param {JSON} schemaIndex A global index of all schema grades
* registered with the framework.
* @param {Array} typeFilter A list of all necessarry top level
* preference names.
* @param {JSON} primarySchema Primary schema provided as an option to
* the primary builder.
* @return {Array} A list of schema grades.
*/
fluid.prefs.primaryBuilder.buildPrimary = function (schemaIndex, typeFilter, primarySchema) {
var suppliedPrimaryGradeName = "fluid.prefs.schemas.suppliedPrimary" + fluid.allocateGuid();
// Create a grade that has a primary schema passed as an option inclosed.
fluid.defaults(suppliedPrimaryGradeName, {
gradeNames: ["fluid.prefs.schemas"],
schema: fluid.filterKeys(primarySchema.properties || primarySchema,
typeFilter, false)
});
var primary = [];
// Lookup all available schema grades from the index that match the
// top level preference name.
fluid.each(typeFilter, function merge(type) {
var schemaGrades = schemaIndex[type];
if (schemaGrades) {
primary.push.apply(primary, schemaGrades);
}
});
primary.push(suppliedPrimaryGradeName);
return primary;
};
/**
* An index function that indexes all shcema grades based on their
* preference name.
* @param {JSON} defaults Registered defaults for a schema grade.
* @return {String} A preference name.
*/
fluid.prefs.primaryBuilder.defaultSchemaIndexer = function (defaults) {
if (defaults.schema) {
return fluid.keys(defaults.schema.properties);
}
};
/*******************************************************************************
* Base primary schema grade
*******************************************************************************/
fluid.defaults("fluid.prefs.schemas", {
gradeNames: ["fluid.component"],
mergePolicy: {
schema: fluid.prefs.schemas.merge
}
});
})(jQuery, fluid_2_0_0);

135
lib/infusion/src/framework/preferences/js/SelfVoicingEnactor.js

@ -0,0 +1,135 @@
/*
Copyright 2013-2015 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/*******************************************************************************
* speak
*
* An enactor that is capable of speaking text.
* Typically this will be used as a base grade to an enactor that supplies
* the text to be spoken.
*******************************************************************************/
fluid.defaults("fluid.prefs.enactor.speak", {
gradeNames: "fluid.prefs.enactor",
preferenceMap: {
"fluid.prefs.speak": {
"model.enabled": "default"
}
},
components: {
tts: {
type: "fluid.textToSpeech",
options: {
model: "{speak}.model",
invokers: {
queueSpeech: {
funcName: "fluid.prefs.enactor.speak.queueSpeech",
args: ["{that}", "fluid.textToSpeech.queueSpeech", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
}
}
}
}
}
});
// Accepts a speechFn (either a function or function name), which will be used to perform the
// underlying queuing of the speech. This allows the SpeechSynthesis to be replaced (e.g. for testing)
fluid.prefs.enactor.speak.queueSpeech = function (that, speechFn, text, interrupt, options) {
// force a string value
var str = text.toString();
// remove extra whitespace
str = str.trim();
str.replace(/\s{2,}/gi, " ");
if (that.model.enabled && str) {
if (typeof(speechFn) === "string") {
fluid.invokeGlobalFunction(speechFn, [that, str, interrupt, options]);
} else {
speechFn(that, str, interrupt, options);
}
}
};
/*******************************************************************************
* selfVoicing
*
* The enactor that enables self voicing of an entire page
*******************************************************************************/
fluid.defaults("fluid.prefs.enactor.selfVoicing", {
gradeNames: ["fluid.prefs.enactor.speak", "fluid.viewComponent"],
modelListeners: {
"enabled": {
listener: "{that}.handleSelfVoicing",
args: ["{change}.value"]
}
},
invokers: {
handleSelfVoicing: {
funcName: "fluid.prefs.enactor.selfVoicing.handleSelfVoicing",
// Pass in invokers to force them to be resolved
args: ["{that}.options.strings.welcomeMsg", "{tts}.queueSpeech", "{that}.readFromDOM", "{tts}.cancel", "{arguments}.0"]
},
readFromDOM: {
funcName: "fluid.prefs.enactor.selfVoicing.readFromDOM",
args: ["{that}", "{that}.container"]
}
},
strings: {
welcomeMsg: "text to speech enabled"
}
});
fluid.prefs.enactor.selfVoicing.handleSelfVoicing = function (welcomeMsg, queueSpeech, readFromDOM, cancel, enabled) {
if (enabled) {
queueSpeech(welcomeMsg, true);
readFromDOM();
} else {
cancel();
}
};
// Constants representing DOM node types.
fluid.prefs.enactor.selfVoicing.nodeType = {
ELEMENT_NODE: 1,
TEXT_NODE: 3
};
// TODO: Currently only reads text nodes and alt text.
// This should be expanded to read other text descriptors as well.
fluid.prefs.enactor.selfVoicing.readFromDOM = function (that, elm) {
elm = $(elm);
var nodes = elm.contents();
fluid.each(nodes, function (node) {
if (node.nodeType === fluid.prefs.enactor.selfVoicing.nodeType.TEXT_NODE && node.nodeValue) {
that.tts.queueSpeech(node.nodeValue);
}
if (node.nodeType === fluid.prefs.enactor.selfVoicing.nodeType.ELEMENT_NODE && window.getComputedStyle(node).display !== "none") {
if (node.nodeName === "IMG") {
var altText = node.getAttribute("alt");
if (altText) {
that.tts.queueSpeech(altText);
}
} else {
fluid.prefs.enactor.selfVoicing.readFromDOM(that, node);
}
}
});
};
})(jQuery, fluid_2_0_0);

39
lib/infusion/src/framework/preferences/js/SelfVoicingPanel.js

@ -0,0 +1,39 @@
/*
Copyright 2014-2015 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/**********************************************************************************
* speakPanel
**********************************************************************************/
fluid.defaults("fluid.prefs.panel.speak", {
gradeNames: ["fluid.prefs.panel"],
preferenceMap: {
"fluid.prefs.speak": {
"model.speak": "default"
}
},
selectors: {
speak: ".flc-prefsEditor-speak",
label: ".flc-prefsEditor-speak-label",
speakDescr: ".flc-prefsEditor-speak-descr"
},
protoTree: {
label: {messagekey: "speakLabel"},
speakDescr: {messagekey: "speakDescr"},
speak: "${speak}"
}
});
})(jQuery, fluid_2_0_0);

69
lib/infusion/src/framework/preferences/js/SelfVoicingSchemas.js

@ -0,0 +1,69 @@
/*
Copyright 2015 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function (fluid) {
"use strict";
/*******************************************************************************
* Starter auxiliary schema grade
*
* Contains the settings for 7 preferences: text size, line space, text font,
* contrast, table of contents, inputs larger and emphasize links
*******************************************************************************/
// Fine-tune the starter aux schema and add speak panel
fluid.defaults("fluid.prefs.auxSchema.speak", {
gradeNames: ["fluid.prefs.auxSchema"],
auxiliarySchema: {
"namespace": "fluid.prefs.constructed",
"terms": {
"templatePrefix": "../../framework/preferences/html/",
"messagePrefix": "../../framework/preferences/messages/"
},
"template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
"message": "%messagePrefix/prefsEditor.json",
speak: {
type: "fluid.prefs.speak",
enactor: {
type: "fluid.prefs.enactor.selfVoicing",
container: "body"
},
panel: {
type: "fluid.prefs.panel.speak",
container: ".flc-prefsEditor-speak",
template: "%templatePrefix/PrefsEditorTemplate-speak.html",
message: "%messagePrefix/speak.json"
}
}
}
});
/*******************************************************************************
* Primary Schema
*******************************************************************************/
// add extra prefs to the starter primary schemas
fluid.defaults("fluid.prefs.schemas.speak", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.speak": {
"type": "boolean",
"default": false
}
}
});
})(fluid_2_0_0);

461
lib/infusion/src/framework/preferences/js/SeparatedPanelPrefsEditor.js

@ -0,0 +1,461 @@
/*
Copyright 2011-2016 OCAD University
Copyright 2011 Lucendo Development Ltd.
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
fluid.registerNamespace("fluid.dom");
fluid.dom.getDocumentHeight = function (dokkument) {
var body = $("body", dokkument)[0];
return body.offsetHeight;
};
/*******************************************************
* Separated Panel Preferences Editor Top Level Driver *
*******************************************************/
fluid.defaults("fluid.prefs.separatedPanel", {
gradeNames: ["fluid.prefs.prefsEditorLoader", "fluid.contextAware"],
events: {
afterRender: null,
onReady: null,
onCreateSlidingPanelReady: {
events: {
iframeRendered: "afterRender",
onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded"
}
},
templatesAndIframeReady: {
events: {
iframeReady: "afterRender",
templatesLoaded: "onPrefsEditorTemplatesLoaded",
messagesLoaded: "onPrefsEditorMessagesLoaded"
}
}
},
lazyLoad: false,
contextAwareness: {
lazyLoad: {
checks: {
lazyLoad: {
contextValue: "{fluid.prefs.separatedPanel}.options.lazyLoad",
gradeNames: "fluid.prefs.separatedPanel.lazyLoad"
}
}
},
separatedPanelPrefsWidgetType: {
checks: {
jQueryUI: {
contextValue: "{fluid.prefsWidgetType}",
equals: "jQueryUI",
gradeNames: "fluid.prefs.separatedPanel.jQueryUI"
}
},
defaultGradeNames: "fluid.prefs.separatedPanel.nativeHTML"
}
},
selectors: {
reset: ".flc-prefsEditor-reset",
iframe: ".flc-prefsEditor-iframe"
},
listeners: {
"onReady.bindEvents": {
listener: "fluid.prefs.separatedPanel.bindEvents",
args: ["{separatedPanel}.prefsEditor", "{iframeRenderer}.iframeEnhancer", "{separatedPanel}"]
},
"onCreate.hideReset": {
listener: "fluid.prefs.separatedPanel.hideReset",
args: ["{separatedPanel}"]
}
},
invokers: {
bindReset: {
funcName: "fluid.bind",
args: ["{separatedPanel}.dom.reset", "click", "{arguments}.0"]
}
},
components: {
slidingPanel: {
type: "fluid.slidingPanel",
container: "{separatedPanel}.container",
createOnEvent: "onCreateSlidingPanelReady",
options: {
gradeNames: ["fluid.prefs.msgLookup"],
strings: {
showText: "{that}.msgLookup.slidingPanelShowText",
hideText: "{that}.msgLookup.slidingPanelHideText",
showTextAriaLabel: "{that}.msgLookup.showTextAriaLabel",
hideTextAriaLabel: "{that}.msgLookup.hideTextAriaLabel",
panelLabel: "{that}.msgLookup.slidingPanelPanelLabel"
},
invokers: {
operateShow: {
funcName: "fluid.prefs.separatedPanel.showPanel",
args: ["{that}.dom.panel", "{that}.events.afterPanelShow.fire"],
// override default implementation
"this": null,
"method": null
},
operateHide: {
funcName: "fluid.prefs.separatedPanel.hidePanel",
args: ["{that}.dom.panel", "{iframeRenderer}.iframe", "{that}.events.afterPanelHide.fire"],
// override default implementation
"this": null,
"method": null
}
},
components: {
msgResolver: {
type: "fluid.messageResolver",
options: {
messageBase: "{messageLoader}.resources.prefsEditor.resourceText"
}
}
}
}
},
iframeRenderer: {
type: "fluid.prefs.separatedPanel.renderIframe",
container: "{separatedPanel}.dom.iframe",
options: {
events: {
afterRender: "{separatedPanel}.events.afterRender"
},
components: {
iframeEnhancer: {
type: "fluid.uiEnhancer",
container: "{iframeRenderer}.renderPrefsEditorContainer",
createOnEvent: "afterRender",
options: {
gradeNames: ["{pageEnhancer}.uiEnhancer.options.userGrades"],
jQuery: "{iframeRenderer}.jQuery",
tocTemplate: "{pageEnhancer}.uiEnhancer.options.tocTemplate"
}
}
}
}
},
prefsEditor: {
createOnEvent: "templatesAndIframeReady",
container: "{iframeRenderer}.renderPrefsEditorContainer",
options: {
gradeNames: ["fluid.prefs.uiEnhancerRelay"],
// ensure that model and applier are available to users at top level
model: "{separatedPanel}.model",
events: {
onSignificantDOMChange: null,
updateEnhancerModel: "{that}.events.modelChanged"
},
listeners: {
"modelChanged.save": "{that}.save",
"onCreate.bindReset": {
listener: "{separatedPanel}.bindReset",
args: ["{that}.reset"]
},
"afterReset.applyChanges": "{that}.applyChanges",
"onReady.boilOnReady": {
listener: "{separatedPanel}.events.onReady",
args: "{separatedPanel}"
}
}
}
}
},
outerEnhancerOptions: "{originalEnhancerOptions}.options.originalUserOptions",
distributeOptions: [{
source: "{that}.options.slidingPanel",
removeSource: true,
target: "{that > slidingPanel}.options"
}, {
source: "{that}.options.iframeRenderer",
removeSource: true,
target: "{that > iframeRenderer}.options"
}, {
source: "{that}.options.iframe",
removeSource: true,
target: "{that}.options.selectors.iframe"
}, {
source: "{that}.options.outerEnhancerOptions",
removeSource: true,
target: "{that iframeEnhancer}.options"
}, {
source: "{that}.options.terms",
target: "{that > iframeRenderer}.options.terms"
}]
});
// Used for context-awareness behaviour
fluid.defaults("fluid.prefs.separatedPanel.nativeHTML", {
components: {
iframeRenderer: {
options: {
markupProps: {
src: "%templatePrefix/SeparatedPanelPrefsEditorFrame-nativeHTML.html"
}
}
}
}
});
// Used for context-awareness behaviour
fluid.defaults("fluid.prefs.separatedPanel.jQueryUI", {
components: {
iframeRenderer: {
options: {
markupProps: {
src: "%templatePrefix/SeparatedPanelPrefsEditorFrame-jQueryUI.html"
}
}
}
}
});
fluid.prefs.separatedPanel.hideReset = function (separatedPanel) {
separatedPanel.locate("reset").hide();
};
/*****************************************
* fluid.prefs.separatedPanel.renderIframe *
*****************************************/
fluid.defaults("fluid.prefs.separatedPanel.renderIframe", {
gradeNames: ["fluid.viewComponent"],
events: {
afterRender: null
},
styles: {
container: "fl-prefsEditor-separatedPanel-iframe"
},
terms: {
templatePrefix: "."
},
markupProps: {
"class": "flc-iframe",
src: "%templatePrefix/prefsEditorIframe.html"
},
listeners: {
"onCreate.startLoadingIframe": "fluid.prefs.separatedPanel.renderIframe.startLoadingIframe"
}
});
fluid.prefs.separatedPanel.renderIframe.startLoadingIframe = function (that) {
var styles = that.options.styles;
// TODO: get earlier access to templateLoader,
that.options.markupProps.src = fluid.stringTemplate(that.options.markupProps.src, that.options.terms);
that.iframeSrc = that.options.markupProps.src;
// Create iframe and append to container
that.iframe = $("<iframe/>");
that.iframe.on("load", function () {
var iframeWindow = that.iframe[0].contentWindow;
that.iframeDocument = iframeWindow.document;
// The iframe should prefer its own version of jQuery if a separate
// one is loaded
that.jQuery = iframeWindow.jQuery || $;
that.renderPrefsEditorContainer = that.jQuery("body", that.iframeDocument);
that.jQuery(that.iframeDocument).ready(that.events.afterRender.fire);
});
that.iframe.attr(that.options.markupProps);
that.iframe.addClass(styles.container);
that.iframe.hide();
that.iframe.appendTo(that.container);
};
fluid.prefs.separatedPanel.updateView = function (prefsEditor) {
prefsEditor.events.onPrefsEditorRefresh.fire();
prefsEditor.events.onSignificantDOMChange.fire();
};
fluid.prefs.separatedPanel.bindEvents = function (prefsEditor, iframeEnhancer, separatedPanel) {
// FLUID-5740: This binding should be done declaratively - needs ginger world in order to bind onto slidingPanel
// which is a child of this component
var separatedPanelId = separatedPanel.slidingPanel.panelId;
separatedPanel.locate("reset").attr({
"aria-controls": separatedPanelId,
"role": "button"
});
separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () {
fluid.prefs.separatedPanel.updateView(prefsEditor);
}, "updateView", "after:openPanel");
prefsEditor.events.onPrefsEditorRefresh.addListener(function () {
iframeEnhancer.updateModel(prefsEditor.model.preferences);
}, "updateModel");
prefsEditor.events.afterReset.addListener(function (prefsEditor) {
fluid.prefs.separatedPanel.updateView(prefsEditor);
}, "updateView");
prefsEditor.events.onSignificantDOMChange.addListener(function () {
var dokkument = prefsEditor.container[0].ownerDocument;
var height = fluid.dom.getDocumentHeight(dokkument);
var iframe = separatedPanel.iframeRenderer.iframe;
var attrs = {height: height + 15}; // TODO: Configurable padding here
var panel = separatedPanel.slidingPanel.locate("panel");
panel.css({height: ""});
iframe.animate(attrs, 400);
}, "adjustHeight");
separatedPanel.slidingPanel.events.afterPanelHide.addListener(function () {
separatedPanel.iframeRenderer.iframe.height(0);
// Prevent the hidden Preferences Editorpanel from being keyboard and screen reader accessible
separatedPanel.iframeRenderer.iframe.hide();
}, "collapseFrame");
separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () {
separatedPanel.iframeRenderer.iframe.show();
separatedPanel.locate("reset").show();
}, "openPanel");
separatedPanel.slidingPanel.events.onPanelHide.addListener(function () {
separatedPanel.locate("reset").hide();
}, "hideReset");
};
// Replace the standard animator since we don't want the panel to become hidden
// (potential cause of jumping)
fluid.prefs.separatedPanel.hidePanel = function (panel, iframe, callback) {
iframe.clearQueue(); // FLUID-5334: clear the animation queue
$(panel).animate({height: 0}, {duration: 400, complete: callback});
};
// no activity - the kickback to the updateView listener will automatically trigger the
// DOMChangeListener above. This ordering is preferable to avoid causing the animation to
// jump by refreshing the view inside the iframe
fluid.prefs.separatedPanel.showPanel = function (panel, callback) {
// A bizarre race condition has emerged under FF where the iframe held within the panel does not
// react synchronously to being shown
fluid.invokeLater(callback);
};
/**
* FLUID-5926: Some of our users have asked for ways to improve the initial page load
* performance when using the separated panel prefs editor / UI Options. One option,
* provided here, is to implement a scheme for lazy loading the instantiation of the
* prefs editor, only instantiating enough of the workflow to allow display the
* sliding panel tab.
*
* fluid.prefs.separatedPanel.lazyLoad modifies the typical separatedPanel workflow
* by delaying the instantiation and loading of resources for the prefs editor until
* the first time it is opened.
*
* Lazy Load Workflow:
*
* - On instantiation of the prefsEditorLoader only the messageLoader and slidingPanel are instantiated
* - On instantiation, the messageLoader only loads preLoadResources, these are the messages required by
* the slidingPanel. The remaining message bundles will not be loaded until the "onLazyLoad" event is fired.
* - After the preLoadResources have been loaded, the onPrefsEditorMessagesPreloaded event is fired, and triggers the
* sliding panel to instantiate.
* - When a user opens the separated panel prefs editor / UI Options, it checks to see if the prefs editor has been
* instantiated. If it hasn't, a listener is temporarily bound to the onReady event, which gets fired
* after the prefs editor is ready. This is used to continue the process of opening the sliding panel for the first time.
* Additionally the onLazyLoad event is fired, which kicks off the remainder of the instantiation process.
* - onLazyLoad triggers the templateLoader to fetch all of the templates and the messageLoader to fetch the remaining
* message bundles. From here the standard instantiation workflow takes place.
*/
fluid.defaults("fluid.prefs.separatedPanel.lazyLoad", {
events: {
onLazyLoad: null,
onPrefsEditorMessagesPreloaded: null,
onCreateSlidingPanelReady: {
events: {
onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesPreloaded"
}
},
templatesAndIframeReady: {
events: {
onLazyLoad: "onLazyLoad"
}
}
},
components: {
templateLoader: {
createOnEvent: "onLazyLoad"
},
messageLoader: {
options: {
events: {
onResourcesPreloaded: "{separatedPanel}.events.onPrefsEditorMessagesPreloaded"
},
preloadResources: "prefsEditor",
listeners: {
"onCreate.loadResources": {
listener: "fluid.prefs.separatedPanel.lazyLoad.preloadResources",
args: ["{that}", {expander: {func: "{that}.resolveResources"}}, "{that}.options.preloadResources"]
},
"{separatedPanel}.events.onLazyLoad": {
listener: "fluid.resourceLoader.loadResources",
args: ["{messageLoader}", {expander: {func: "{messageLoader}.resolveResources"}}]
}
}
}
},
slidingPanel: {
options: {
invokers: {
operateShow: {
funcName: "fluid.prefs.separatedPanel.lazyLoad.showPanel",
args: ["{separatedPanel}", "{that}.events.afterPanelShow.fire"]
}
}
}
}
}
});
fluid.prefs.separatedPanel.lazyLoad.showPanel = function (separatedPanel, callback) {
if (separatedPanel.prefsEditor) {
fluid.invokeLater(callback);
} else {
separatedPanel.events.onReady.addListener(function (that) {
that.events.onReady.removeListener("showPanelCallBack");
fluid.invokeLater(callback);
}, "showPanelCallBack");
separatedPanel.events.onLazyLoad.fire();
}
};
/**
* Used to override the standard "onCreate.loadResources" listener for fluid.resourceLoader component,
* allowing for pre-loading of a subset of resources. This is required for the lazyLoading workflow
* for the "fluid.prefs.separatedPanel.lazyLoad".
*
* @param {Object} that - the component
* @param {Object} resource - all of the resourceSpecs to load, including preload and others.
* see: fluid.fetchResources
* @param {Array/String} toPreload - a String or an Array of Strings corresponding to the names
* of the resources, supplied in the resource argument, that
* should be loaded. Only these resources will be loaded.
*/
fluid.prefs.separatedPanel.lazyLoad.preloadResources = function (that, resources, toPreload) {
toPreload = fluid.makeArray(toPreload);
var preloadResources = {};
fluid.each(toPreload, function (resourceName) {
preloadResources[resourceName] = resources[resourceName];
});
// This portion of code was copied from fluid.resourceLoader.loadResources
// and will likely need to track any changes made there.
fluid.fetchResources(preloadResources, function () {
that.resources = preloadResources;
that.events.onResourcesPreloaded.fire(preloadResources);
});
};
})(jQuery, fluid_2_0_0);

409
lib/infusion/src/framework/preferences/js/StarterGrades.js

@ -0,0 +1,409 @@
/*
Copyright 2013-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/*******************************************************************************
* Starter prefsEditor Model
*
* Provides the default values for the starter prefsEditor model
*******************************************************************************/
fluid.defaults("fluid.prefs.initialModel.starter", {
gradeNames: ["fluid.prefs.initialModel"],
members: {
// TODO: This information is supposed to be generated from the JSON
// schema describing various preferences. For now it's kept in top
// level prefsEditor to avoid further duplication.
initialModel: {
preferences: {
textFont: "default", // key from classname map
theme: "default", // key from classname map
textSize: 1, // in points
lineSpace: 1, // in ems
toc: false, // boolean
links: false, // boolean
inputsLarger: false // boolean
}
}
}
});
/*******************************************************************************
* CSSClassEnhancerBase
*
* Provides the map between the settings and css classes to be applied.
* Used as a UIEnhancer base grade that can be pulled in as requestd.
*******************************************************************************/
fluid.defaults("fluid.uiEnhancer.cssClassEnhancerBase", {
gradeNames: ["fluid.component"],
classnameMap: {
"textFont": {
"default": "",
"times": "fl-font-times",
"comic": "fl-font-comic-sans",
"arial": "fl-font-arial",
"verdana": "fl-font-verdana"
},
"theme": {
"default": "fl-theme-prefsEditor-default",
"bw": "fl-theme-bw",
"wb": "fl-theme-wb",
"by": "fl-theme-by",
"yb": "fl-theme-yb",
"lgdg": "fl-theme-lgdg"
},
"links": "fl-link-enhanced",
"inputsLarger": "fl-text-larger"
}
});
/*******************************************************************************
* BrowserTextEnhancerBase
*
* Provides the default font size translation between the strings and actual pixels.
* Used as a UIEnhancer base grade that can be pulled in as requestd.
*******************************************************************************/
fluid.defaults("fluid.uiEnhancer.browserTextEnhancerBase", {
gradeNames: ["fluid.component"],
fontSizeMap: {
"xx-small": "9px",
"x-small": "11px",
"small": "13px",
"medium": "15px",
"large": "18px",
"x-large": "23px",
"xx-large": "30px"
}
});
/*******************************************************************************
* UI Enhancer Starter Enactors
*
* A grade component for UIEnhancer. It is a collection of default UI Enhancer
* action ants.
*******************************************************************************/
fluid.defaults("fluid.uiEnhancer.starterEnactors", {
gradeNames: ["fluid.uiEnhancer", "fluid.uiEnhancer.cssClassEnhancerBase", "fluid.uiEnhancer.browserTextEnhancerBase"],
model: "{fluid.prefs.initialModel}.initialModel.preferences",
components: {
textSize: {
type: "fluid.prefs.enactor.textSize",
container: "{uiEnhancer}.container",
options: {
fontSizeMap: "{uiEnhancer}.options.fontSizeMap",
model: {
value: "{uiEnhancer}.model.textSize"
}
}
},
textFont: {
type: "fluid.prefs.enactor.textFont",
container: "{uiEnhancer}.container",
options: {
classes: "{uiEnhancer}.options.classnameMap.textFont",
model: {
value: "{uiEnhancer}.model.textFont"
}
}
},
lineSpace: {
type: "fluid.prefs.enactor.lineSpace",
container: "{uiEnhancer}.container",
options: {
fontSizeMap: "{uiEnhancer}.options.fontSizeMap",
model: {
value: "{uiEnhancer}.model.lineSpace"
}
}
},
contrast: {
type: "fluid.prefs.enactor.contrast",
container: "{uiEnhancer}.container",
options: {
classes: "{uiEnhancer}.options.classnameMap.theme",
model: {
value: "{uiEnhancer}.model.theme"
}
}
},
emphasizeLinks: {
type: "fluid.prefs.enactor.emphasizeLinks",
container: "{uiEnhancer}.container",
options: {
cssClass: "{uiEnhancer}.options.classnameMap.links",
model: {
value: "{uiEnhancer}.model.links"
}
}
},
inputsLarger: {
type: "fluid.prefs.enactor.inputsLarger",
container: "{uiEnhancer}.container",
options: {
cssClass: "{uiEnhancer}.options.classnameMap.inputsLarger",
model: {
value: "{uiEnhancer}.model.inputsLarger"
}
}
},
tableOfContents: {
type: "fluid.prefs.enactor.tableOfContents",
container: "{uiEnhancer}.container",
options: {
tocTemplate: "{uiEnhancer}.options.tocTemplate",
model: {
toc: "{uiEnhancer}.model.toc"
}
}
}
}
});
/*********************************************************************************************************
* Starter Settings Panels
*
* A collection of all the default Preferences Editorsetting panels.
*********************************************************************************************************/
fluid.defaults("fluid.prefs.starterPanels", {
gradeNames: ["fluid.prefs.prefsEditor"],
selectors: {
textSize: ".flc-prefsEditor-text-size",
textFont: ".flc-prefsEditor-text-font",
lineSpace: ".flc-prefsEditor-line-space",
contrast: ".flc-prefsEditor-contrast",
textControls: ".flc-prefsEditor-text-controls",
layoutControls: ".flc-prefsEditor-layout-controls",
linksControls: ".flc-prefsEditor-links-controls"
},
components: {
textSize: {
type: "fluid.prefs.panel.textSize",
container: "{prefsEditor}.dom.textSize",
createOnEvent: "onPrefsEditorMarkupReady",
options: {
gradeNames: "fluid.prefs.prefsEditorConnections",
model: {
textSize: "{prefsEditor}.model.preferences.textSize"
},
messageBase: "{messageLoader}.resources.textSize.resourceText",
resources: {
template: "{templateLoader}.resources.textSize"
}
}
},
lineSpace: {
type: "fluid.prefs.panel.lineSpace",
container: "{prefsEditor}.dom.lineSpace",
createOnEvent: "onPrefsEditorMarkupReady",
options: {
gradeNames: "fluid.prefs.prefsEditorConnections",
model: {
lineSpace: "{prefsEditor}.model.preferences.lineSpace"
},
messageBase: "{messageLoader}.resources.lineSpace.resourceText",
resources: {
template: "{templateLoader}.resources.lineSpace"
}
}
},
textFont: {
type: "fluid.prefs.panel.textFont",
container: "{prefsEditor}.dom.textFont",
createOnEvent: "onPrefsEditorMarkupReady",
options: {
gradeNames: "fluid.prefs.prefsEditorConnections",
classnameMap: "{uiEnhancer}.options.classnameMap",
model: {
value: "{prefsEditor}.model.preferences.textFont"
},
messageBase: "{messageLoader}.resources.textFont.resourceText",
resources: {
template: "{templateLoader}.resources.textFont"
}
}
},
contrast: {
type: "fluid.prefs.panel.contrast",
container: "{prefsEditor}.dom.contrast",
createOnEvent: "onPrefsEditorMarkupReady",
options: {
gradeNames: "fluid.prefs.prefsEditorConnections",
classnameMap: "{uiEnhancer}.options.classnameMap",
model: {
value: "{prefsEditor}.model.preferences.theme"
},
messageBase: "{messageLoader}.resources.contrast.resourceText",
resources: {
template: "{templateLoader}.resources.contrast"
}
}
},
layoutControls: {
type: "fluid.prefs.panel.layoutControls",
container: "{prefsEditor}.dom.layoutControls",
createOnEvent: "onPrefsEditorMarkupReady",
options: {
gradeNames: "fluid.prefs.prefsEditorConnections",
model: {
toc: "{prefsEditor}.model.preferences.toc"
},
messageBase: "{messageLoader}.resources.layoutControls.resourceText",
resources: {
template: "{templateLoader}.resources.layoutControls"
}
}
},
linksControls: {
type: "fluid.prefs.panel.linksControls",
container: "{prefsEditor}.dom.linksControls",
createOnEvent: "onPrefsEditorMarkupReady",
options: {
gradeNames: "fluid.prefs.prefsEditorConnections",
selectors: {
emphasizeLinks: ".flc-prefsEditor-emphasizeLinks",
inputsLarger: ".flc-prefsEditor-inputsLarger"
},
selectorsToIgnore: ["emphasizeLinks", "inputsLarger"],
model: {
fluid_prefs_emphasizeLinks: "{prefsEditor}.model.preferences.links",
fluid_prefs_inputsLarger: "{prefsEditor}.model.preferences.inputsLarger"
},
components: {
emphasizeLinks: {
type: "fluid.prefs.panel.emphasizeLinks",
container: "{that}.dom.emphasizeLinks",
createOnEvent: "initSubPanels",
options: {
messageBase: "{messageLoader}.resources.emphasizeLinks.resourceText"
}
},
inputsLarger: {
type: "fluid.prefs.panel.inputsLarger",
container: "{that}.dom.inputsLarger",
createOnEvent: "initSubPanels",
options: {
messageBase: "{messageLoader}.resources.inputsLarger.resourceText"
}
}
},
messageBase: "{messageLoader}.resources.linksControls.resourceText",
resources: {
template: "{templateLoader}.resources.linksControls",
emphasizeLinks: "{templateLoader}.resources.emphasizeLinks",
inputsLarger: "{templateLoader}.resources.inputsLarger"
}
}
}
}
});
/******************************
* Starter Template Loader
******************************/
/**
* A template loader component that expands the resources blocks for loading resources used by starterPanels
*
* @param {Object} options
*/
fluid.defaults("fluid.prefs.starterTemplateLoader", {
gradeNames: ["fluid.resourceLoader", "fluid.contextAware"],
resources: {
textFont: "%templatePrefix/PrefsEditorTemplate-textFont.html",
contrast: "%templatePrefix/PrefsEditorTemplate-contrast.html",
layoutControls: "%templatePrefix/PrefsEditorTemplate-layout.html",
linksControls: "%templatePrefix/PrefsEditorTemplate-linksControls.html",
emphasizeLinks: "%templatePrefix/PrefsEditorTemplate-emphasizeLinks.html",
inputsLarger: "%templatePrefix/PrefsEditorTemplate-inputsLarger.html"
},
contextAwareness: {
startTemplateLoaderPrefsWidgetType: {
checks: {
jQueryUI: {
contextValue: "{fluid.prefsWidgetType}",
equals: "jQueryUI",
gradeNames: "fluid.prefs.starterTemplateLoader.jQuery"
}
},
defaultGradeNames: "fluid.prefs.starterTemplateLoader.native"
}
}
});
fluid.defaults("fluid.prefs.starterTemplateLoader.native", {
resources: {
textSize: "%templatePrefix/PrefsEditorTemplate-textSize-nativeHTML.html",
lineSpace: "%templatePrefix/PrefsEditorTemplate-lineSpace-nativeHTML.html"
}
});
fluid.defaults("fluid.prefs.starterTemplateLoader.jQuery", {
resources: {
textSize: "%templatePrefix/PrefsEditorTemplate-textSize-jQueryUI.html",
lineSpace: "%templatePrefix/PrefsEditorTemplate-lineSpace-jQueryUI.html"
}
});
fluid.defaults("fluid.prefs.starterSeparatedPanelTemplateLoader", {
gradeNames: ["fluid.prefs.starterTemplateLoader"],
resources: {
prefsEditor: "%templatePrefix/SeparatedPanelPrefsEditor.html"
}
});
fluid.defaults("fluid.prefs.starterFullPreviewTemplateLoader", {
gradeNames: ["fluid.prefs.starterTemplateLoader"],
resources: {
prefsEditor: "%templatePrefix/FullPreviewPrefsEditor.html"
}
});
fluid.defaults("fluid.prefs.starterFullNoPreviewTemplateLoader", {
gradeNames: ["fluid.prefs.starterTemplateLoader"],
resources: {
prefsEditor: "%templatePrefix/FullNoPreviewPrefsEditor.html"
}
});
/******************************
* Starter Message Loader
******************************/
/**
* A message loader component that expands the resources blocks for loading messages for starter panels
*
* @param {Object} options
*/
fluid.defaults("fluid.prefs.starterMessageLoader", {
gradeNames: ["fluid.resourceLoader"],
resources: {
prefsEditor: "%messagePrefix/prefsEditor.json",
textSize: "%messagePrefix/textSize.json",
textFont: "%messagePrefix/textFont.json",
lineSpace: "%messagePrefix/lineSpace.json",
contrast: "%messagePrefix/contrast.json",
layoutControls: "%messagePrefix/tableOfContents.json",
linksControls: "%messagePrefix/linksControls.json",
emphasizeLinks: "%messagePrefix/emphasizeLinks.json",
inputsLarger: "%messagePrefix/inputsLarger.json"
}
});
})(jQuery, fluid_2_0_0);

325
lib/infusion/src/framework/preferences/js/StarterSchemas.js

@ -0,0 +1,325 @@
/*
Copyright 2013-2016 OCAD University
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function (fluid) {
"use strict";
/*******************************************************************************
* Starter auxiliary schema grade
*
* Contains the settings for 7 preferences: text size, line space, text font,
* contrast, table of contents, inputs larger and emphasize links
*******************************************************************************/
fluid.defaults("fluid.prefs.termsAware");
// textSize mixin (base)
fluid.defaults("fluid.prefs.auxSchema.starter.textSize", {
gradeNames: ["fluid.contextAware"],
auxiliarySchema: {
"textSize": {
"type": "fluid.prefs.textSize",
"enactor": {
"type": "fluid.prefs.enactor.textSize"
},
"panel": {
"type": "fluid.prefs.panel.textSize",
"container": ".flc-prefsEditor-text-size", // the css selector in the template where the panel is rendered
"message": "%messagePrefix/textSize.json"
}
}
},
contextAwareness: {
textSizeSliderVariety: {
checks: {
jQueryUI: {
contextValue: "{fluid.prefsWidgetType}",
equals: "jQueryUI",
gradeNames: "fluid.prefs.auxSchema.starter.textSize.jQueryUI"
}
},
defaultGradeNames: "fluid.prefs.auxSchema.starter.textSize.nativeHTML"
}
}
});
fluid.defaults("fluid.prefs.auxSchema.starter.textSize.nativeHTML", {
auxiliarySchema: {
"textSize": {
"panel": {
"template": "%templatePrefix/PrefsEditorTemplate-textSize-nativeHTML.html"
}
}
}
});
fluid.defaults("fluid.prefs.auxSchema.starter.textSize.jQueryUI", {
auxiliarySchema: {
"textSize": {
"panel": {
"template": "%templatePrefix/PrefsEditorTemplate-textSize-jQueryUI.html"
}
}
}
});
// lineSpace mixin (base)
fluid.defaults("fluid.prefs.auxSchema.starter.lineSpace", {
gradeNames: ["fluid.contextAware"],
auxiliarySchema: {
"lineSpace": {
"type": "fluid.prefs.lineSpace",
"enactor": {
"type": "fluid.prefs.enactor.lineSpace",
"fontSizeMap": {
"xx-small": "9px",
"x-small": "11px",
"small": "13px",
"medium": "15px",
"large": "18px",
"x-large": "23px",
"xx-large": "30px"
}
},
"panel": {
"type": "fluid.prefs.panel.lineSpace",
"container": ".flc-prefsEditor-line-space", // the css selector in the template where the panel is rendered
"message": "%messagePrefix/lineSpace.json"
}
}
},
contextAwareness: {
lineSpaceSliderVariety: {
checks: {
jQueryUI: {
contextValue: "{fluid.prefsWidgetType}",
equals: "jQueryUI",
gradeNames: "fluid.prefs.auxSchema.starter.lineSpace.jQueryUI"
}
},
defaultGradeNames: "fluid.prefs.auxSchema.starter.lineSpace.nativeHTML"
}
}
});
fluid.defaults("fluid.prefs.auxSchema.starter.lineSpace.nativeHTML", {
auxiliarySchema: {
"lineSpace": {
"panel": {
"template": "%templatePrefix/PrefsEditorTemplate-lineSpace-nativeHTML.html"
}
}
}
});
fluid.defaults("fluid.prefs.auxSchema.starter.lineSpace.jQueryUI", {
auxiliarySchema: {
"lineSpace": {
"panel": {
"template": "%templatePrefix/PrefsEditorTemplate-lineSpace-jQueryUI.html"
}
}
}
});
fluid.defaults("fluid.prefs.auxSchema.starter", {
gradeNames: ["fluid.prefs.auxSchema", "fluid.prefs.auxSchema.starter.lineSpace", "fluid.prefs.auxSchema.starter.textSize"],
auxiliarySchema: {
"loaderGrades": ["fluid.prefs.separatedPanel"],
"namespace": "fluid.prefs.constructed", // The author of the auxiliary schema will provide this and will be the component to call to initialize the constructed PrefsEditor.
"terms": {
"templatePrefix": "../../framework/preferences/html", // Must match the keyword used below to identify the common path to settings panel templates.
"messagePrefix": "../../framework/preferences/messages" // Must match the keyword used below to identify the common path to message files.
},
"template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
"message": "%messagePrefix/prefsEditor.json",
"textFont": {
"type": "fluid.prefs.textFont",
"classes": {
"default": "",
"times": "fl-font-times",
"comic": "fl-font-comic-sans",
"arial": "fl-font-arial",
"verdana": "fl-font-verdana"
},
"enactor": {
"type": "fluid.prefs.enactor.textFont",
"classes": "@textFont.classes"
},
"panel": {
"type": "fluid.prefs.panel.textFont",
"container": ".flc-prefsEditor-text-font", // the css selector in the template where the panel is rendered
"classnameMap": {"textFont": "@textFont.classes"},
"template": "%templatePrefix/PrefsEditorTemplate-textFont.html",
"message": "%messagePrefix/textFont.json"
}
},
"contrast": {
"type": "fluid.prefs.contrast",
"classes": {
"default": "fl-theme-prefsEditor-default",
"bw": "fl-theme-bw",
"wb": "fl-theme-wb",
"by": "fl-theme-by",
"yb": "fl-theme-yb",
"lgdg": "fl-theme-lgdg"
},
"enactor": {
"type": "fluid.prefs.enactor.contrast",
"classes": "@contrast.classes"
},
"panel": {
"type": "fluid.prefs.panel.contrast",
"container": ".flc-prefsEditor-contrast", // the css selector in the template where the panel is rendered
"classnameMap": {"theme": "@contrast.classes"},
"template": "%templatePrefix/PrefsEditorTemplate-contrast.html",
"message": "%messagePrefix/contrast.json"
}
},
"tableOfContents": {
"type": "fluid.prefs.tableOfContents",
"enactor": {
"type": "fluid.prefs.enactor.tableOfContents",
"tocTemplate": "../../components/tableOfContents/html/TableOfContents.html"
},
"panel": {
"type": "fluid.prefs.panel.layoutControls",
"container": ".flc-prefsEditor-layout-controls", // the css selector in the template where the panel is rendered
"template": "%templatePrefix/PrefsEditorTemplate-layout.html",
"message": "%messagePrefix/tableOfContents.json"
}
},
"emphasizeLinks": {
"type": "fluid.prefs.emphasizeLinks",
"enactor": {
"type": "fluid.prefs.enactor.emphasizeLinks",
"cssClass": "fl-link-enhanced"
},
"panel": {
"type": "fluid.prefs.panel.emphasizeLinks",
"container": ".flc-prefsEditor-emphasizeLinks", // the css selector in the template where the panel is rendered
"template": "%templatePrefix/PrefsEditorTemplate-emphasizeLinks.html",
"message": "%messagePrefix/emphasizeLinks.json"
}
},
"inputsLarger": {
"type": "fluid.prefs.inputsLarger",
"enactor": {
"type": "fluid.prefs.enactor.inputsLarger",
"cssClass": "fl-text-larger"
},
"panel": {
"type": "fluid.prefs.panel.inputsLarger",
"container": ".flc-prefsEditor-inputsLarger", // the css selector in the template where the panel is rendered
"template": "%templatePrefix/PrefsEditorTemplate-inputsLarger.html",
"message": "%messagePrefix/inputsLarger.json"
}
},
groups: {
"linksControls": {
"container": ".flc-prefsEditor-links-controls",
"template": "%templatePrefix/PrefsEditorTemplate-linksControls.html",
"message": "%messagePrefix/linksControls.json",
"type": "fluid.prefs.panel.linksControls",
"panels": ["emphasizeLinks", "inputsLarger"]
}
}
}
});
/*******************************************************************************
* Starter primary schema grades
*
* Contains the settings for 7 preferences: text size, line space, text font,
* contrast, table of contents, inputs larger and emphasize links
*******************************************************************************/
fluid.defaults("fluid.prefs.schemas.textSize", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.textSize": {
"type": "number",
"default": 1,
"minimum": 1,
"maximum": 2,
"divisibleBy": 0.1
}
}
});
fluid.defaults("fluid.prefs.schemas.lineSpace", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.lineSpace": {
"type": "number",
"default": 1,
"minimum": 1,
"maximum": 2,
"divisibleBy": 0.1
}
}
});
fluid.defaults("fluid.prefs.schemas.textFont", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.textFont": {
"type": "string",
"default": "default",
"enum": ["default", "times", "comic", "arial", "verdana"]
}
}
});
fluid.defaults("fluid.prefs.schemas.contrast", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.contrast": {
"type": "string",
"default": "default",
"enum": ["default", "bw", "wb", "by", "yb", "lgdg"]
}
}
});
fluid.defaults("fluid.prefs.schemas.tableOfContents", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.tableOfContents": {
"type": "boolean",
"default": false
}
}
});
fluid.defaults("fluid.prefs.schemas.emphasizeLinks", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.emphasizeLinks": {
"type": "boolean",
"default": false
}
}
});
fluid.defaults("fluid.prefs.schemas.inputsLarger", {
gradeNames: ["fluid.prefs.schemas"],
schema: {
"fluid.prefs.inputsLarger": {
"type": "boolean",
"default": false
}
}
});
})(fluid_2_0_0);

168
lib/infusion/src/framework/preferences/js/Store.js

@ -0,0 +1,168 @@
/*
Copyright 2009 University of Toronto
Copyright 2011-2013 OCAD University
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/**
* A Generic data source grade that defines an API for getting and setting
* data.
*/
// TODO: unify with Kettle's and ultimately Infusion's dataSource
fluid.defaults("fluid.prefs.dataSource", {
gradeNames: ["fluid.component"],
invokers: {
get: "fluid.notImplemented",
set: "fluid.notImplemented"
}
});
fluid.defaults("fluid.prefs.store", {
gradeNames: ["fluid.prefs.dataSource", "fluid.contextAware"],
contextAwareness: {
strategy: {
defaultGradeNames: "fluid.prefs.cookieStore"
}
}
});
/****************
* Cookie Store *
****************/
/**
* SettingsStore Subcomponent that uses a cookie for persistence.
* @param {Object} options
*/
fluid.defaults("fluid.prefs.cookieStore", {
gradeNames: ["fluid.prefs.store"],
cookie: {
name: "fluid-ui-settings",
path: "/",
expires: ""
},
invokers: {
get: {
funcName: "fluid.prefs.cookieStore.get",
args: "{that}.options.cookie.name"
},
set: {
funcName: "fluid.prefs.cookieStore.set",
args: ["{arguments}.0", "{that}.options.cookie"]
}
}
});
/**
* Retrieve and return the value of the cookie
*/
fluid.prefs.cookieStore.get = function (cookieName) {
var cookie = document.cookie;
if (cookie.length <= 0) {
return;
}
var cookiePrefix = cookieName + "=";
var startIndex = cookie.indexOf(cookiePrefix);
if (startIndex < 0) {
return;
}
startIndex = startIndex + cookiePrefix.length;
var endIndex = cookie.indexOf(";", startIndex);
if (endIndex < startIndex) {
endIndex = cookie.length;
}
var cookieSection = cookie.substring(startIndex, endIndex);
var togo;
try {
togo = JSON.parse(decodeURIComponent(cookieSection));
} catch (e) {
fluid.log("Error parsing cookie " + cookieSection + " as JSON - clearing");
document.cookie = "";
}
return togo;
};
/**
* Assembles the cookie string
* @param {Object} cookie settings
*/
fluid.prefs.cookieStore.assembleCookie = function (cookieOptions) {
var cookieStr = cookieOptions.name + "=" + cookieOptions.data;
if (cookieOptions.expires) {
cookieStr += "; expires=" + cookieOptions.expires;
}
if (cookieOptions.path) {
cookieStr += "; path=" + cookieOptions.path;
}
return cookieStr;
};
/**
* Saves the settings into a cookie
* @param {Object} settings
* @param {Object} cookieOptions
*/
fluid.prefs.cookieStore.set = function (settings, cookieOptions) {
cookieOptions.data = encodeURIComponent(JSON.stringify(settings));
document.cookie = fluid.prefs.cookieStore.assembleCookie(cookieOptions);
};
/**************
* Temp Store *
**************/
/**
* SettingsStore mock that doesn't do persistence.
* @param {Object} options
*/
fluid.defaults("fluid.prefs.tempStore", {
gradeNames: ["fluid.prefs.store", "fluid.modelComponent"],
invokers: {
get: {
funcName: "fluid.identity",
args: "{that}.model"
},
set: {
funcName: "fluid.prefs.tempStore.set",
args: ["{arguments}.0", "{that}.applier"]
}
}
});
fluid.prefs.tempStore.set = function (settings, applier) {
applier.fireChangeRequest({path: "", type: "DELETE"});
applier.change("", settings);
};
fluid.defaults("fluid.prefs.globalSettingsStore", {
gradeNames: ["fluid.component"],
components: {
settingsStore: {
type: "fluid.prefs.store",
options: {
gradeNames: ["fluid.resolveRootSingle"],
singleRootType: "fluid.prefs.store"
}
}
}
});
})(jQuery, fluid_2_0_0);

118
lib/infusion/src/framework/preferences/js/UIEnhancer.js

@ -0,0 +1,118 @@
/*
Copyright 2009 University of Toronto
Copyright 2010-2015 OCAD University
Copyright 2011 Lucendo Development Ltd.
Copyright 2015 Raising the Floor - International
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/
var fluid_2_0_0 = fluid_2_0_0 || {};
(function ($, fluid) {
"use strict";
/*******************************************************************************
* Root Model *
* *
* Holds the default values for enactors and panel model values *
*******************************************************************************/
fluid.defaults("fluid.prefs.initialModel", {
gradeNames: ["fluid.component"],
members: {
// TODO: This information is supposed to be generated from the JSON
// schema describing various preferences. For now it's kept in top
// level prefsEditor to avoid further duplication.
initialModel: {
preferences: {} // To keep initial preferences
}
}
});
/***********************************************
* UI Enhancer *
* *
* Transforms the page based on user settings. *
***********************************************/
fluid.defaults("fluid.uiEnhancer", {
gradeNames: ["fluid.viewComponent"],
invokers: {
updateModel: {
func: "{that}.applier.change",
args: ["", "{arguments}.0"]
}
},
userGrades: "@expand:fluid.prefs.filterEnhancerGrades({that}.options.gradeNames)"
});
// Make this a standalone grade since options merging can't see 2 levels deep into merging
// trees and will currently trash "gradeNames" for 2nd level nested components!
fluid.defaults("fluid.uiEnhancer.root", {
gradeNames: ["fluid.uiEnhancer", "fluid.resolveRootSingle"],
singleRootType: "fluid.uiEnhancer"
});
fluid.uiEnhancer.ignorableGrades = ["fluid.uiEnhancer", "fluid.uiEnhancer.root", "fluid.resolveRoot", "fluid.resolveRootSingle"];
// These function is necessary so that we can "clone" a UIEnhancer (e.g. one in an iframe) from another.
// This reflects a long-standing mistake in UIEnhancer design - we should separate the logic in an enhancer
// from a particular binding onto a container.
fluid.prefs.filterEnhancerGrades = function (gradeNames) {
return fluid.remove_if(fluid.makeArray(gradeNames), function (gradeName) {
return fluid.frameworkGrades.indexOf(gradeName) !== -1 || fluid.uiEnhancer.ignorableGrades.indexOf(gradeName) !== -1;
});
};
// This just the options that we are clear safely represent user options - naturally this all has
// to go when we refactor UIEnhancer
fluid.prefs.filterEnhancerOptions = function (options) {
return fluid.filterKeys(options, ["classnameMap", "fontSizeMap", "tocTemplate", "components"]);
};
/********************************************************************************
* PageEnhancer *
* *
* A UIEnhancer wrapper that concerns itself with the entire page. *
* *
* "originalEnhancerOptions" is a grade component to keep track of the original *
* uiEnhancer user options *
********************************************************************************/
// TODO: Both the pageEnhancer and the uiEnhancer need to be available separately - some
// references to "{uiEnhancer}" are present in prefsEditorConnections, whilst other
// sites refer to "{pageEnhancer}". The fact that uiEnhancer requires "body" prevents it
// being top-level until we have the options flattening revolution. Also one day we want
// to make good of advertising an unmerged instance of the "originalEnhancerOptions"
fluid.defaults("fluid.pageEnhancer", {
gradeNames: ["fluid.component", "fluid.originalEnhancerOptions",
"fluid.prefs.initialModel", "fluid.prefs.settingsGetter",
"fluid.resolveRootSingle"],
distributeOptions: {
source: "{that}.options.uiEnhancer",
target: "{that > uiEnhancer}.options"
},
singleRootType: "fluid.pageEnhancer",
components: {
uiEnhancer: {
type: "fluid.uiEnhancer.root",
container: "body"
}
},
originalUserOptions: "@expand:fluid.prefs.filterEnhancerOptions({uiEnhancer}.options)",
listeners: {
"onCreate.initModel": "fluid.pageEnhancer.init"
}
});
fluid.pageEnhancer.init = function (that) {
that.uiEnhancer.updateModel(fluid.get(that.getSettings(), "preferences"));
};
})(jQuery, fluid_2_0_0);

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

Loading…
Cancel
Save