diff --git a/.gitignore b/.gitignore index b9d7869..fbe6c1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Include your project-specific ignores in this file # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files .cache-loader -bower_components +dist node_modules npm-debug.log yarn-error.log diff --git a/.travis.yml b/.travis.yml index 1658132..e03081e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ branches: php: - 7.1 - 7.0 - - 5.6 - nightly env: - TRAVIS_NODE_VERSION="6" diff --git a/app/admin.php b/app/admin.php index ba6979a..d8b08d5 100644 --- a/app/admin.php +++ b/app/admin.php @@ -1,6 +1,6 @@ classes @@ -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); @@ -30,7 +25,7 @@ add_filter('body_class', function (array $classes) { * Add "… Continued" to the excerpt */ add_filter('excerpt_more', function () { - return ' … ' . __('Continued', 'pressbooks-aldine') . ''; + return ' … ' . __('Continued', 'aldine') . ''; }); /** @@ -68,3 +63,25 @@ add_filter('comments_template', function ($comments_template) { ); return template_path(locate_template(["views/{$comments_template}", $comments_template]) ?: $comments_template); }); + +/** + * Remove Admin Bar callback + */ +add_action('admin_bar_init', function () { + remove_action('wp_head', '_admin_bar_bump_cb'); +}); + +/** + * Remove Emoji + * @see https://wordpress.stackexchange.com/questions/185577/disable-emojicons-introduced-with-wp-4-2 + */ +add_action('init', function () { + remove_action('admin_print_styles', 'print_emoji_styles'); + remove_action('wp_head', 'print_emoji_detection_script', 7); + remove_action('admin_print_scripts', 'print_emoji_detection_script'); + remove_action('wp_print_styles', 'print_emoji_styles'); + remove_filter('wp_mail', 'wp_staticize_emoji_for_email'); + remove_filter('the_content_feed', 'wp_staticize_emoji'); + remove_filter('comment_text_rss', 'wp_staticize_emoji'); + add_filter('emoji_svg_url', '__return_false'); +}); diff --git a/app/helpers.php b/app/helpers.php index 71f4e24..a998192 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,6 +1,6 @@ getUri($asset); } +/** + * @param $asset + * @return string + */ +function svg_path($asset) +{ + return sage('assets')->get($asset); +} + /** * @param string|string[] $templates Possible template files * @return array @@ -127,14 +136,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; -} diff --git a/app/setup.php b/app/setup.php index 14da9be..4d48ae2 100644 --- a/app/setup.php +++ b/app/setup.php @@ -1,6 +1,6 @@ __('Primary Navigation', 'pressbooks-aldine') - ]); - /** * Enable post thumbnails * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ @@ -85,20 +99,36 @@ add_action('widgets_init', function () { 'after_title' => '' ]; register_sidebar([ - 'name' => __('Home Block One', 'pressbooks-aldine'), - 'id' => 'home-block-one' + 'name' => __('Home Block 1', 'aldine'), + 'id' => 'home-block-1' + ] + $config); + register_sidebar([ + 'name' => __('Home Block 2', 'aldine'), + 'id' => 'home-block-2' + ] + $config); + register_sidebar([ + 'name' => __('Home Block 3', 'aldine'), + 'id' => 'home-block-3' + ] + $config); + register_sidebar([ + 'name' => __('Home Block 4', 'aldine'), + 'id' => 'home-block-4' + ] + $config); + register_sidebar([ + 'name' => __('Home Block 5', 'aldine'), + 'id' => 'home-block-5' ] + $config); register_sidebar([ - 'name' => __('Home Block Two', 'pressbooks-aldine'), - 'id' => 'home-block-two' + 'name' => __('Network Footer Block 1', 'aldine'), + 'id' => 'network-footer-block-1' ] + $config); register_sidebar([ - 'name' => __('Home Block Three', 'pressbooks-aldine'), - 'id' => 'home-block-three' + 'name' => __('Network Footer Block 2', 'aldine'), + 'id' => 'network-footer-block-2' ] + $config); register_sidebar([ - 'name' => __('Home Block Four', 'pressbooks-aldine'), - 'id' => 'home-block-four' + 'name' => __('Network Footer Block 3', 'aldine'), + 'id' => 'network-footer-block-3' ] + $config); }); diff --git a/app/widgets.php b/app/widgets.php new file mode 100644 index 0000000..1daaf00 --- /dev/null +++ b/app/widgets.php @@ -0,0 +1,24 @@ + esc_html__('Your network’s latest books.', 'aldine') + ]); + } + + /** + * Front-end display of widget. + * + * @see WP_Widget::widget() + * + * @param array $args Widget arguments. + * @param array $instance Saved values from database. + */ + public function widget($args, $instance) + { + $number = (! empty($instance['number'])) ? absint($instance['number']) : 3; + if (!$number) { + $number = 3; + } + if (empty($instance['title'])) { + $instance['title'] = __('Latest Books', 'aldine'); + } + echo $args['before_widget']; + echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title']; + $books = wp_remote_get(home_url('/wp-json/pressbooks/v2/books')); + $books = json_decode($books['body'], true); + echo '
'; + for ($i = 0; $i < $number; $i++) { + printf( + '
+ TK + %2$s + %3$s +
', + $books[$i]['link'], + $books[$i]['metadata']['name'], + __('About this book →', 'aldine') + ); + } + echo '
'; + echo $args['after_widget']; + } + + /** + * Back-end widget form. + * + * @see WP_Widget::form() + * + * @param array $instance Previously saved values from database. + */ + public function form($instance) + { + $title = ! empty($instance['title']) ? $instance['title'] : ''; + $number = ! empty($instance['number']) ? absint($instance['number']) : 3; ?> +

+

+

+

+ esc_html__('Add a styled button which links to a custom URL.', 'aldine') + ]); + } + + /** + * Front-end display of widget. + * + * @see WP_Widget::widget() + * + * @param array $args Widget arguments. + * @param array $instance Saved values from database. + */ + public function widget($args, $instance) + { + echo $args['before_widget']; + if (! empty($instance['url']) && ! empty($instance['title'])) { + printf( + '%2$s', + $instance['url'], + apply_filters('widget_title', $instance['title']) + ); + } + echo $args['after_widget']; + } + + /** + * Back-end widget form. + * + * @see WP_Widget::form() + * + * @param array $instance Previously saved values from database. + */ + public function form($instance) + { + $title = ! empty($instance['title']) ? $instance['title'] : ''; + $url = ! empty($instance['url']) ? $instance['url'] : ''; ?> +

+

+

+

+ esc_html__('Add a styled button which links to a page.', 'aldine') + ]); + } + + /** + * Front-end display of widget. + * + * @see WP_Widget::widget() + * + * @param array $args Widget arguments. + * @param array $instance Saved values from database. + */ + public function widget($args, $instance) + { + echo $args['before_widget']; + if (! empty($instance['page_id'])) { + if (empty($instance['title'])) { + $instance['title'] = get_the_title($instance['page_id']); + } + printf( + '%2$s', + get_permalink($instance['page_id']), + apply_filters('widget_title', $instance['title']) + ); + } + echo $args['after_widget']; + } + + /** + * Back-end widget form. + * + * @see WP_Widget::form() + * + * @param array $instance Previously saved values from database. + */ + public function form($instance) + { + $title = ! empty($instance['title']) ? $instance['title'] : ''; + $page_id = ! empty($instance['page_id']) ? $instance['page_id'] : ''; ?> +

+

+

+

+ =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": [], diff --git a/dist/assets.json b/dist/assets.json deleted file mode 100644 index 73c0f7d..0000000 --- a/dist/assets.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts/customizer.js": "scripts/customizer_d08f6b6b.js", - "styles/main.css": "styles/main_d08f6b6b.css", - "scripts/main.js": "scripts/main_d08f6b6b.js" -} \ No newline at end of file diff --git a/dist/scripts/customizer_d08f6b6b.js b/dist/scripts/customizer_d08f6b6b.js deleted file mode 100644 index eb7b7a2..0000000 --- a/dist/scripts/customizer_d08f6b6b.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/app/themes/pressbooks-aldine/dist/",t(t.s=9)}({0:function(e,t){e.exports=jQuery},10:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),o=n.n(r);wp.customize("blogname",function(e){e.bind(function(e){return o()(".brand").text(e)})})},9:function(e,t,n){e.exports=n(10)}}); \ No newline at end of file diff --git a/dist/scripts/main_d08f6b6b.js b/dist/scripts/main_d08f6b6b.js deleted file mode 100644 index fe0c14b..0000000 --- a/dist/scripts/main_d08f6b6b.js +++ /dev/null @@ -1 +0,0 @@ -!function(t){function n(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return t[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}var e={};n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:o})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="/app/themes/pressbooks-aldine/dist/",n(n.s=1)}([function(t,n){t.exports=jQuery},function(t,n,e){e(2),t.exports=e(8)},function(t,n,e){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),function(t){var n=e(0),o=(e.n(n),e(3)),i=e(5),r=e(6),u=e(7),c=new o.a({common:i.a,home:r.a,aboutUs:u.a});t(document).ready(function(){return c.loadEvents()})}.call(n,e(0))},function(t,n,e){"use strict";var o=e(4),i=function(t){this.routes=t};i.prototype.fire=function(t,n,e){void 0===n&&(n="init"),""!==t&&this.routes[t]&&"function"==typeof this.routes[t][n]&&this.routes[t][n](e)},i.prototype.loadEvents=function(){var t=this;this.fire("common"),document.body.className.toLowerCase().replace(/-/g,"_").split(/\s+/).map(o.a).forEach(function(n){t.fire(n),t.fire(n,"finalize")}),this.fire("common","finalize")},n.a=i},function(t,n,e){"use strict";n.a=function(t){return""+t.charAt(0).toLowerCase()+t.replace(/[\W_]/g,"|").split("|").map(function(t){return""+t.charAt(0).toUpperCase()+t.slice(1)}).join("").slice(1)}},function(t,n,e){"use strict";n.a={init:function(){},finalize:function(){}}},function(t,n,e){"use strict";n.a={init:function(){},finalize:function(){}}},function(t,n,e){"use strict";n.a={init:function(){}}},function(t,n){}]); \ No newline at end of file diff --git a/dist/styles/main_d08f6b6b.css b/dist/styles/main_d08f6b6b.css deleted file mode 100644 index 2e20ca7..0000000 --- a/dist/styles/main_d08f6b6b.css +++ /dev/null @@ -1 +0,0 @@ -body{color:#444;font-family:Spectral,serif}h1,h2,h3,h4,h5,h6{font-family:Karla,sans-serif}h1{font-size:30px;font-weight:600;letter-spacing:2px;line-height:36px;text-transform:uppercase}h1,h2{margin:0}h2{font-size:16px;font-weight:400;line-height:30px}a{color:#b01109}.alignnone{height:auto;margin-left:0;margin-right:0;max-width:100%}.aligncenter{display:block;height:auto;margin:1rem auto}.alignleft,.alignright{height:auto;margin-bottom:1rem}@media (min-width:30rem){.alignleft{float:left;margin-right:1rem}.alignright{float:right;margin-left:1rem}}.screen-reader-text{background:#fff;border:0;clip:rect(0,0,0,0);color:#000;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.banner{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;height:560px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.banner h1{text-align:center}.banner h1 a{text-decoration:none}.banner h2,.home .block h3{text-align:center}.home .block h3{font-size:30px;font-weight:600;letter-spacing:2px;line-height:36px;margin:0;text-transform:uppercase}.home .block h3:before{color:#b01109;display:block;height:5px;width:46px}body#tinymce{margin:12px!important} \ No newline at end of file diff --git a/lib/infusion/Infusion-LICENSE.txt b/lib/infusion/Infusion-LICENSE.txt new file mode 100644 index 0000000..5e9a8fe --- /dev/null +++ b/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 diff --git a/lib/infusion/README.md b/lib/infusion/README.md new file mode 100644 index 0000000..84e1e0b --- /dev/null +++ b/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? ## + + + +## 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 . + +## 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-.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 +``` diff --git a/lib/infusion/ReleaseNotes.md b/lib/infusion/ReleaseNotes.md new file mode 100644 index 0000000..18d6b08 --- /dev/null +++ b/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 #### + + + Testing Configurations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Testing TaskDesktop BrowserMobile Browser
ChromeFirefoxIE 11MS EdgeSafariChrome for AndroidSafari iOS
Run All Unit TestsChrome 54 (macOS 10.12)Firefox 49 (macOS 10.12)IE 11 (Win 10)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)Chrome 54 (Android 6.0.1 & 7.0.0)Safari (iOS 10.1.1)
Smoke Tests - All Manual TestsChrome 54 (macOS 10.11.6)Firefox 50 (macOS 10.12.1)IE 11 (Win 8.1)MS Edge 38 (Win 10)Safari 10 (macOS 10.12.1)Chrome 54 (Android 6.0.1)Safari (iOS 10.1.1)
Smoke Tests - All DemosChrome 54 (macOS 10.12.1)Firefox 50 (macOS 10.12.1)IE 11 (Win 8.1)MS Edge 38 (Win 10)Safari 10 (macOS 10.12.1)Chrome 54 (Android 6.0.1)Safari (iOS 10.1.1)
Smoke Tests - All ExamplesChrome 54 (macOS 10.12.1)Firefox 50 (macOS 10.12.1)IE 11 (Win 8.1)MS Edge 38 (Win 10)Safari 10 (macOS 10.12.1)Chrome 54 (Android 6.0.1)Safari (iOS 10.1.1)
Inline Edit QA Test Plan - Simple TextChrome 54 (macOS 10.10)Firefox 49 (openSUSE Linux 42.1)IE 11 (Win 8.1)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Keyboard Accessibility QA Test PlanChrome 54 (Win 10)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 8.1)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Pager QA Test PlanChrome 54 (Win 10)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Progress QA Test PlanChrome 54 (macOS 10.11.6)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Reorderer QA Test Plan - Image ReordererChrome 54 (Win 10)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Reorderer QA Test Plan - Layout ReordererChrome 54 (macOS 10.11.6)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Reorderer QA Test Plan - List ReordererChrome 54 (macOS 10.11.6)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Reorderer QA Test Plan - Grid ReordererChrome 54 (macOS 10.11.6)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Preferences Framework QA Test PlanChrome 54 (Win 10)Firefox 49.0.2 (macOS 10.12)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
UI Options QA Test Plan - Separated PanelChrome 54 (Win 10)Firefox 49.0.2 (Win 10)IE 11 (Win 7)MS Edge 38 (Win 10)Safari 10 (macOS 10.12)N/AN/A
Uploader QA Test PlanChrome 54 (macOS 10.12.1)Firefox 49.0.2 (macOS 10.12.1)IE 11 (Win 10)MS Edge 38 (Win 10)Safari 10 (macOS 10.12.1)N/AN/A
+ +## 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) diff --git a/lib/infusion/infusion-uiOptions.js b/lib/infusion/infusion-uiOptions.js new file mode 100644 index 0000000..e96482f --- /dev/null +++ b/lib/infusion/infusion-uiOptions.js @@ -0,0 +1,30 @@ +/*! + infusion - v2.0.0 + Thursday, December 8th, 2016, 3:42:03 PM + branch: HEAD + revision: 85a1ffe +*/ + +!function(global,factory){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=global.document?factory(global,!0):function(w){if(!w.document)throw new Error("jQuery requires a window with a document");return factory(w)}:factory(global)}("undefined"!=typeof window?window:this,function(window,noGlobal){"use strict";function DOMEval(code,doc){doc=doc||document;var script=doc.createElement("script");script.text=code,doc.head.appendChild(script).parentNode.removeChild(script)}function isArrayLike(obj){var length=!!obj&&"length"in obj&&obj.length,type=jQuery.type(obj);return"function"!==type&&!jQuery.isWindow(obj)&&("array"===type||0===length||"number"==typeof length&&length>0&&length-1 in obj)}function winnow(elements,qualifier,not){if(jQuery.isFunction(qualifier))return jQuery.grep(elements,function(elem,i){return!!qualifier.call(elem,i,elem)!==not});if(qualifier.nodeType)return jQuery.grep(elements,function(elem){return elem===qualifier!==not});if("string"==typeof qualifier){if(risSimple.test(qualifier))return jQuery.filter(qualifier,elements,not);qualifier=jQuery.filter(qualifier,elements)}return jQuery.grep(elements,function(elem){return indexOf.call(qualifier,elem)>-1!==not&&1===elem.nodeType})}function sibling(cur,dir){for(;(cur=cur[dir])&&1!==cur.nodeType;);return cur}function createOptions(options){var object={};return jQuery.each(options.match(rnotwhite)||[],function(_,flag){object[flag]=!0}),object}function Identity(v){return v}function Thrower(ex){throw ex}function adoptValue(value,resolve,reject){var method;try{value&&jQuery.isFunction(method=value.promise)?method.call(value).done(resolve).fail(reject):value&&jQuery.isFunction(method=value.then)?method.call(value,resolve,reject):resolve.call(void 0,value)}catch(value){reject.call(void 0,value)}}function completed(){document.removeEventListener("DOMContentLoaded",completed),window.removeEventListener("load",completed),jQuery.ready()}function Data(){this.expando=jQuery.expando+Data.uid++}function dataAttr(elem,key,data){var name;if(void 0===data&&1===elem.nodeType)if(name="data-"+key.replace(rmultiDash,"-$&").toLowerCase(),data=elem.getAttribute(name),"string"==typeof data){try{data="true"===data||"false"!==data&&("null"===data?null:+data+""===data?+data:rbrace.test(data)?JSON.parse(data):data)}catch(e){}dataUser.set(elem,key,data)}else data=void 0;return data}function adjustCSS(elem,prop,valueParts,tween){var adjusted,scale=1,maxIterations=20,currentValue=tween?function(){return tween.cur()}:function(){return jQuery.css(elem,prop,"")},initial=currentValue(),unit=valueParts&&valueParts[3]||(jQuery.cssNumber[prop]?"":"px"),initialInUnit=(jQuery.cssNumber[prop]||"px"!==unit&&+initial)&&rcssNum.exec(jQuery.css(elem,prop));if(initialInUnit&&initialInUnit[3]!==unit){unit=unit||initialInUnit[3],valueParts=valueParts||[],initialInUnit=+initial||1;do scale=scale||".5",initialInUnit/=scale,jQuery.style(elem,prop,initialInUnit+unit);while(scale!==(scale=currentValue()/initial)&&1!==scale&&--maxIterations)}return valueParts&&(initialInUnit=+initialInUnit||+initial||0,adjusted=valueParts[1]?initialInUnit+(valueParts[1]+1)*valueParts[2]:+valueParts[2],tween&&(tween.unit=unit,tween.start=initialInUnit,tween.end=adjusted)),adjusted}function getDefaultDisplay(elem){var temp,doc=elem.ownerDocument,nodeName=elem.nodeName,display=defaultDisplayMap[nodeName];return display?display:(temp=doc.body.appendChild(doc.createElement(nodeName)),display=jQuery.css(temp,"display"),temp.parentNode.removeChild(temp),"none"===display&&(display="block"),defaultDisplayMap[nodeName]=display,display)}function showHide(elements,show){for(var display,elem,values=[],index=0,length=elements.length;index-1)ignored&&ignored.push(elem);else if(contains=jQuery.contains(elem.ownerDocument,elem),tmp=getAll(fragment.appendChild(elem),"script"),contains&&setGlobalEval(tmp),scripts)for(j=0;elem=tmp[j++];)rscriptType.test(elem.type||"")&&scripts.push(elem);return fragment}function returnTrue(){return!0}function returnFalse(){return!1}function safeActiveElement(){try{return document.activeElement}catch(err){}}function on(elem,types,selector,data,fn,one){var origFn,type;if("object"==typeof types){"string"!=typeof selector&&(data=data||selector,selector=void 0);for(type in types)on(elem,type,selector,data,types[type],one);return elem}if(null==data&&null==fn?(fn=selector,data=selector=void 0):null==fn&&("string"==typeof selector?(fn=data,data=void 0):(fn=data,data=selector,selector=void 0)),fn===!1)fn=returnFalse;else if(!fn)return elem;return 1===one&&(origFn=fn,fn=function(event){return jQuery().off(event),origFn.apply(this,arguments)},fn.guid=origFn.guid||(origFn.guid=jQuery.guid++)),elem.each(function(){jQuery.event.add(this,types,fn,data,selector)})}function manipulationTarget(elem,content){return jQuery.nodeName(elem,"table")&&jQuery.nodeName(11!==content.nodeType?content:content.firstChild,"tr")?elem.getElementsByTagName("tbody")[0]||elem:elem}function disableScript(elem){return elem.type=(null!==elem.getAttribute("type"))+"/"+elem.type,elem}function restoreScript(elem){var match=rscriptTypeMasked.exec(elem.type);return match?elem.type=match[1]:elem.removeAttribute("type"),elem}function cloneCopyEvent(src,dest){var i,l,type,pdataOld,pdataCur,udataOld,udataCur,events;if(1===dest.nodeType){if(dataPriv.hasData(src)&&(pdataOld=dataPriv.access(src),pdataCur=dataPriv.set(dest,pdataOld),events=pdataOld.events)){delete pdataCur.handle,pdataCur.events={};for(type in events)for(i=0,l=events[type].length;i1&&"string"==typeof value&&!support.checkClone&&rchecked.test(value))return collection.each(function(index){var self=collection.eq(index);isFunction&&(args[0]=value.call(this,index,self.html())),domManip(self,args,callback,ignored)});if(l&&(fragment=buildFragment(args,collection[0].ownerDocument,!1,collection,ignored),first=fragment.firstChild,1===fragment.childNodes.length&&(fragment=first),first||ignored)){for(scripts=jQuery.map(getAll(fragment,"script"),disableScript),hasScripts=scripts.length;i=0&&jExpr.cacheLength&&delete cache[keys.shift()],cache[key+" "]=value}var keys=[];return cache}function markFunction(fn){return fn[expando]=!0,fn}function assert(fn){var el=document.createElement("fieldset");try{return!!fn(el)}catch(e){return!1}finally{el.parentNode&&el.parentNode.removeChild(el),el=null}}function addHandle(attrs,handler){for(var arr=attrs.split("|"),i=arr.length;i--;)Expr.attrHandle[arr[i]]=handler}function siblingCheck(a,b){var cur=b&&a,diff=cur&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(diff)return diff;if(cur)for(;cur=cur.nextSibling;)if(cur===b)return-1;return a?1:-1}function createInputPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return"input"===name&&elem.type===type}}function createButtonPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return("input"===name||"button"===name)&&elem.type===type}}function createDisabledPseudo(disabled){return function(elem){return"label"in elem&&elem.disabled===disabled||"form"in elem&&elem.disabled===disabled||"form"in elem&&elem.disabled===!1&&(elem.isDisabled===disabled||elem.isDisabled!==!disabled&&("label"in elem||!disabledAncestor(elem))!==disabled)}}function createPositionalPseudo(fn){return markFunction(function(argument){return argument=+argument,markFunction(function(seed,matches){for(var j,matchIndexes=fn([],seed.length,argument),i=matchIndexes.length;i--;)seed[j=matchIndexes[i]]&&(seed[j]=!(matches[j]=seed[j]))})})}function testContext(context){return context&&"undefined"!=typeof context.getElementsByTagName&&context}function setFilters(){}function toSelector(tokens){for(var i=0,len=tokens.length,selector="";i1?function(elem,context,xml){for(var i=matchers.length;i--;)if(!matchers[i](elem,context,xml))return!1;return!0}:matchers[0]}function multipleContexts(selector,contexts,results){for(var i=0,len=contexts.length;i-1&&(seed[temp]=!(results[temp]=elem)); +}}else matcherOut=condense(matcherOut===results?matcherOut.splice(preexisting,matcherOut.length):matcherOut),postFinder?postFinder(null,results,matcherOut,xml):push.apply(results,matcherOut)})}function matcherFromTokens(tokens){for(var checkContext,matcher,j,len=tokens.length,leadingRelative=Expr.relative[tokens[0].type],implicitRelative=leadingRelative||Expr.relative[" "],i=leadingRelative?1:0,matchContext=addCombinator(function(elem){return elem===checkContext},implicitRelative,!0),matchAnyContext=addCombinator(function(elem){return indexOf(checkContext,elem)>-1},implicitRelative,!0),matchers=[function(elem,context,xml){var ret=!leadingRelative&&(xml||context!==outermostContext)||((checkContext=context).nodeType?matchContext(elem,context,xml):matchAnyContext(elem,context,xml));return checkContext=null,ret}];i1&&elementMatcher(matchers),i>1&&toSelector(tokens.slice(0,i-1).concat({value:" "===tokens[i-2].type?"*":""})).replace(rtrim,"$1"),matcher,i0,byElement=elementMatchers.length>0,superMatcher=function(seed,context,xml,results,outermost){var elem,j,matcher,matchedCount=0,i="0",unmatched=seed&&[],setMatched=[],contextBackup=outermostContext,elems=seed||byElement&&Expr.find.TAG("*",outermost),dirrunsUnique=dirruns+=null==contextBackup?1:Math.random()||.1,len=elems.length;for(outermost&&(outermostContext=context===document||context||outermost);i!==len&&null!=(elem=elems[i]);i++){if(byElement&&elem){for(j=0,context||elem.ownerDocument===document||(setDocument(elem),xml=!documentIsHTML);matcher=elementMatchers[j++];)if(matcher(elem,context||document,xml)){results.push(elem);break}outermost&&(dirruns=dirrunsUnique)}bySet&&((elem=!matcher&&elem)&&matchedCount--,seed&&unmatched.push(elem))}if(matchedCount+=i,bySet&&i!==matchedCount){for(j=0;matcher=setMatchers[j++];)matcher(unmatched,setMatched,context,xml);if(seed){if(matchedCount>0)for(;i--;)unmatched[i]||setMatched[i]||(setMatched[i]=pop.call(results));setMatched=condense(setMatched)}push.apply(results,setMatched),outermost&&!seed&&setMatched.length>0&&matchedCount+setMatchers.length>1&&Sizzle.uniqueSort(results)}return outermost&&(dirruns=dirrunsUnique,outermostContext=contextBackup),unmatched};return bySet?markFunction(superMatcher):superMatcher}var i,support,Expr,getText,isXML,tokenize,compile,select,outermostContext,sortInput,hasDuplicate,setDocument,document,docElem,documentIsHTML,rbuggyQSA,rbuggyMatches,matches,contains,expando="sizzle"+1*new Date,preferredDoc=window.document,dirruns=0,done=0,classCache=createCache(),tokenCache=createCache(),compilerCache=createCache(),sortOrder=function(a,b){return a===b&&(hasDuplicate=!0),0},hasOwn={}.hasOwnProperty,arr=[],pop=arr.pop,push_native=arr.push,push=arr.push,slice=arr.slice,indexOf=function(list,elem){for(var i=0,len=list.length;i+~]|"+whitespace+")"+whitespace+"*"),rattributeQuotes=new RegExp("="+whitespace+"*([^\\]'\"]*?)"+whitespace+"*\\]","g"),rpseudo=new RegExp(pseudos),ridentifier=new RegExp("^"+identifier+"$"),matchExpr={ID:new RegExp("^#("+identifier+")"),CLASS:new RegExp("^\\.("+identifier+")"),TAG:new RegExp("^("+identifier+"|[*])"),ATTR:new RegExp("^"+attributes),PSEUDO:new RegExp("^"+pseudos),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+whitespace+"*(even|odd|(([+-]|)(\\d*)n|)"+whitespace+"*(?:([+-]|)"+whitespace+"*(\\d+)|))"+whitespace+"*\\)|)","i"),bool:new RegExp("^(?:"+booleans+")$","i"),needsContext:new RegExp("^"+whitespace+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+whitespace+"*((?:-\\d)?\\d*)"+whitespace+"*\\)|)(?=[^-]|$)","i")},rinputs=/^(?:input|select|textarea|button)$/i,rheader=/^h\d$/i,rnative=/^[^{]+\{\s*\[native \w/,rquickExpr=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,rsibling=/[+~]/,runescape=new RegExp("\\\\([\\da-f]{1,6}"+whitespace+"?|("+whitespace+")|.)","ig"),funescape=function(_,escaped,escapedWhitespace){var high="0x"+escaped-65536;return high!==high||escapedWhitespace?escaped:high<0?String.fromCharCode(high+65536):String.fromCharCode(high>>10|55296,1023&high|56320)},rcssescape=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,fcssescape=function(ch,asCodePoint){return asCodePoint?"\0"===ch?"�":ch.slice(0,-1)+"\\"+ch.charCodeAt(ch.length-1).toString(16)+" ":"\\"+ch},unloadHandler=function(){setDocument()},disabledAncestor=addCombinator(function(elem){return elem.disabled===!0},{dir:"parentNode",next:"legend"});try{push.apply(arr=slice.call(preferredDoc.childNodes),preferredDoc.childNodes),arr[preferredDoc.childNodes.length].nodeType}catch(e){push={apply:arr.length?function(target,els){push_native.apply(target,slice.call(els))}:function(target,els){for(var j=target.length,i=0;target[j++]=els[i++];);target.length=j-1}}}support=Sizzle.support={},isXML=Sizzle.isXML=function(elem){var documentElement=elem&&(elem.ownerDocument||elem).documentElement;return!!documentElement&&"HTML"!==documentElement.nodeName},setDocument=Sizzle.setDocument=function(node){var hasCompare,subWindow,doc=node?node.ownerDocument||node:preferredDoc;return doc!==document&&9===doc.nodeType&&doc.documentElement?(document=doc,docElem=document.documentElement,documentIsHTML=!isXML(document),preferredDoc!==document&&(subWindow=document.defaultView)&&subWindow.top!==subWindow&&(subWindow.addEventListener?subWindow.addEventListener("unload",unloadHandler,!1):subWindow.attachEvent&&subWindow.attachEvent("onunload",unloadHandler)),support.attributes=assert(function(el){return el.className="i",!el.getAttribute("className")}),support.getElementsByTagName=assert(function(el){return el.appendChild(document.createComment("")),!el.getElementsByTagName("*").length}),support.getElementsByClassName=rnative.test(document.getElementsByClassName),support.getById=assert(function(el){return docElem.appendChild(el).id=expando,!document.getElementsByName||!document.getElementsByName(expando).length}),support.getById?(Expr.find.ID=function(id,context){if("undefined"!=typeof context.getElementById&&documentIsHTML){var m=context.getElementById(id);return m?[m]:[]}},Expr.filter.ID=function(id){var attrId=id.replace(runescape,funescape);return function(elem){return elem.getAttribute("id")===attrId}}):(delete Expr.find.ID,Expr.filter.ID=function(id){var attrId=id.replace(runescape,funescape);return function(elem){var node="undefined"!=typeof elem.getAttributeNode&&elem.getAttributeNode("id");return node&&node.value===attrId}}),Expr.find.TAG=support.getElementsByTagName?function(tag,context){return"undefined"!=typeof context.getElementsByTagName?context.getElementsByTagName(tag):support.qsa?context.querySelectorAll(tag):void 0}:function(tag,context){var elem,tmp=[],i=0,results=context.getElementsByTagName(tag);if("*"===tag){for(;elem=results[i++];)1===elem.nodeType&&tmp.push(elem);return tmp}return results},Expr.find.CLASS=support.getElementsByClassName&&function(className,context){if("undefined"!=typeof context.getElementsByClassName&&documentIsHTML)return context.getElementsByClassName(className)},rbuggyMatches=[],rbuggyQSA=[],(support.qsa=rnative.test(document.querySelectorAll))&&(assert(function(el){docElem.appendChild(el).innerHTML="",el.querySelectorAll("[msallowcapture^='']").length&&rbuggyQSA.push("[*^$]="+whitespace+"*(?:''|\"\")"),el.querySelectorAll("[selected]").length||rbuggyQSA.push("\\["+whitespace+"*(?:value|"+booleans+")"),el.querySelectorAll("[id~="+expando+"-]").length||rbuggyQSA.push("~="),el.querySelectorAll(":checked").length||rbuggyQSA.push(":checked"),el.querySelectorAll("a#"+expando+"+*").length||rbuggyQSA.push(".#.+[+~]")}),assert(function(el){el.innerHTML="";var input=document.createElement("input");input.setAttribute("type","hidden"),el.appendChild(input).setAttribute("name","D"),el.querySelectorAll("[name=d]").length&&rbuggyQSA.push("name"+whitespace+"*[*^$|!~]?="),2!==el.querySelectorAll(":enabled").length&&rbuggyQSA.push(":enabled",":disabled"),docElem.appendChild(el).disabled=!0,2!==el.querySelectorAll(":disabled").length&&rbuggyQSA.push(":enabled",":disabled"),el.querySelectorAll("*,:x"),rbuggyQSA.push(",.*:")})),(support.matchesSelector=rnative.test(matches=docElem.matches||docElem.webkitMatchesSelector||docElem.mozMatchesSelector||docElem.oMatchesSelector||docElem.msMatchesSelector))&&assert(function(el){support.disconnectedMatch=matches.call(el,"*"),matches.call(el,"[s!='']:x"),rbuggyMatches.push("!=",pseudos)}),rbuggyQSA=rbuggyQSA.length&&new RegExp(rbuggyQSA.join("|")),rbuggyMatches=rbuggyMatches.length&&new RegExp(rbuggyMatches.join("|")),hasCompare=rnative.test(docElem.compareDocumentPosition),contains=hasCompare||rnative.test(docElem.contains)?function(a,b){var adown=9===a.nodeType?a.documentElement:a,bup=b&&b.parentNode;return a===bup||!(!bup||1!==bup.nodeType||!(adown.contains?adown.contains(bup):a.compareDocumentPosition&&16&a.compareDocumentPosition(bup)))}:function(a,b){if(b)for(;b=b.parentNode;)if(b===a)return!0;return!1},sortOrder=hasCompare?function(a,b){if(a===b)return hasDuplicate=!0,0;var compare=!a.compareDocumentPosition-!b.compareDocumentPosition;return compare?compare:(compare=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&compare||!support.sortDetached&&b.compareDocumentPosition(a)===compare?a===document||a.ownerDocument===preferredDoc&&contains(preferredDoc,a)?-1:b===document||b.ownerDocument===preferredDoc&&contains(preferredDoc,b)?1:sortInput?indexOf(sortInput,a)-indexOf(sortInput,b):0:4&compare?-1:1)}:function(a,b){if(a===b)return hasDuplicate=!0,0;var cur,i=0,aup=a.parentNode,bup=b.parentNode,ap=[a],bp=[b];if(!aup||!bup)return a===document?-1:b===document?1:aup?-1:bup?1:sortInput?indexOf(sortInput,a)-indexOf(sortInput,b):0;if(aup===bup)return siblingCheck(a,b);for(cur=a;cur=cur.parentNode;)ap.unshift(cur);for(cur=b;cur=cur.parentNode;)bp.unshift(cur);for(;ap[i]===bp[i];)i++;return i?siblingCheck(ap[i],bp[i]):ap[i]===preferredDoc?-1:bp[i]===preferredDoc?1:0},document):document},Sizzle.matches=function(expr,elements){return Sizzle(expr,null,null,elements)},Sizzle.matchesSelector=function(elem,expr){if((elem.ownerDocument||elem)!==document&&setDocument(elem),expr=expr.replace(rattributeQuotes,"='$1']"),support.matchesSelector&&documentIsHTML&&!compilerCache[expr+" "]&&(!rbuggyMatches||!rbuggyMatches.test(expr))&&(!rbuggyQSA||!rbuggyQSA.test(expr)))try{var ret=matches.call(elem,expr);if(ret||support.disconnectedMatch||elem.document&&11!==elem.document.nodeType)return ret}catch(e){}return Sizzle(expr,document,null,[elem]).length>0},Sizzle.contains=function(context,elem){return(context.ownerDocument||context)!==document&&setDocument(context),contains(context,elem)},Sizzle.attr=function(elem,name){(elem.ownerDocument||elem)!==document&&setDocument(elem);var fn=Expr.attrHandle[name.toLowerCase()],val=fn&&hasOwn.call(Expr.attrHandle,name.toLowerCase())?fn(elem,name,!documentIsHTML):void 0;return void 0!==val?val:support.attributes||!documentIsHTML?elem.getAttribute(name):(val=elem.getAttributeNode(name))&&val.specified?val.value:null},Sizzle.escape=function(sel){return(sel+"").replace(rcssescape,fcssescape)},Sizzle.error=function(msg){throw new Error("Syntax error, unrecognized expression: "+msg)},Sizzle.uniqueSort=function(results){var elem,duplicates=[],j=0,i=0;if(hasDuplicate=!support.detectDuplicates,sortInput=!support.sortStable&&results.slice(0),results.sort(sortOrder),hasDuplicate){for(;elem=results[i++];)elem===results[i]&&(j=duplicates.push(i));for(;j--;)results.splice(duplicates[j],1)}return sortInput=null,results},getText=Sizzle.getText=function(elem){var node,ret="",i=0,nodeType=elem.nodeType;if(nodeType){if(1===nodeType||9===nodeType||11===nodeType){if("string"==typeof elem.textContent)return elem.textContent;for(elem=elem.firstChild;elem;elem=elem.nextSibling)ret+=getText(elem)}else if(3===nodeType||4===nodeType)return elem.nodeValue}else for(;node=elem[i++];)ret+=getText(node);return ret},Expr=Sizzle.selectors={cacheLength:50,createPseudo:markFunction,match:matchExpr,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(match){return match[1]=match[1].replace(runescape,funescape),match[3]=(match[3]||match[4]||match[5]||"").replace(runescape,funescape),"~="===match[2]&&(match[3]=" "+match[3]+" "),match.slice(0,4)},CHILD:function(match){return match[1]=match[1].toLowerCase(),"nth"===match[1].slice(0,3)?(match[3]||Sizzle.error(match[0]),match[4]=+(match[4]?match[5]+(match[6]||1):2*("even"===match[3]||"odd"===match[3])),match[5]=+(match[7]+match[8]||"odd"===match[3])):match[3]&&Sizzle.error(match[0]),match},PSEUDO:function(match){var excess,unquoted=!match[6]&&match[2];return matchExpr.CHILD.test(match[0])?null:(match[3]?match[2]=match[4]||match[5]||"":unquoted&&rpseudo.test(unquoted)&&(excess=tokenize(unquoted,!0))&&(excess=unquoted.indexOf(")",unquoted.length-excess)-unquoted.length)&&(match[0]=match[0].slice(0,excess),match[2]=unquoted.slice(0,excess)),match.slice(0,3))}},filter:{TAG:function(nodeNameSelector){var nodeName=nodeNameSelector.replace(runescape,funescape).toLowerCase();return"*"===nodeNameSelector?function(){return!0}:function(elem){return elem.nodeName&&elem.nodeName.toLowerCase()===nodeName}},CLASS:function(className){var pattern=classCache[className+" "];return pattern||(pattern=new RegExp("(^|"+whitespace+")"+className+"("+whitespace+"|$)"))&&classCache(className,function(elem){return pattern.test("string"==typeof elem.className&&elem.className||"undefined"!=typeof elem.getAttribute&&elem.getAttribute("class")||"")})},ATTR:function(name,operator,check){return function(elem){var result=Sizzle.attr(elem,name);return null==result?"!="===operator:!operator||(result+="","="===operator?result===check:"!="===operator?result!==check:"^="===operator?check&&0===result.indexOf(check):"*="===operator?check&&result.indexOf(check)>-1:"$="===operator?check&&result.slice(-check.length)===check:"~="===operator?(" "+result.replace(rwhitespace," ")+" ").indexOf(check)>-1:"|="===operator&&(result===check||result.slice(0,check.length+1)===check+"-"))}},CHILD:function(type,what,argument,first,last){var simple="nth"!==type.slice(0,3),forward="last"!==type.slice(-4),ofType="of-type"===what;return 1===first&&0===last?function(elem){return!!elem.parentNode}:function(elem,context,xml){var cache,uniqueCache,outerCache,node,nodeIndex,start,dir=simple!==forward?"nextSibling":"previousSibling",parent=elem.parentNode,name=ofType&&elem.nodeName.toLowerCase(),useCache=!xml&&!ofType,diff=!1;if(parent){if(simple){for(;dir;){for(node=elem;node=node[dir];)if(ofType?node.nodeName.toLowerCase()===name:1===node.nodeType)return!1;start=dir="only"===type&&!start&&"nextSibling"}return!0}if(start=[forward?parent.firstChild:parent.lastChild],forward&&useCache){for(node=parent,outerCache=node[expando]||(node[expando]={}),uniqueCache=outerCache[node.uniqueID]||(outerCache[node.uniqueID]={}),cache=uniqueCache[type]||[],nodeIndex=cache[0]===dirruns&&cache[1],diff=nodeIndex&&cache[2],node=nodeIndex&&parent.childNodes[nodeIndex];node=++nodeIndex&&node&&node[dir]||(diff=nodeIndex=0)||start.pop();)if(1===node.nodeType&&++diff&&node===elem){uniqueCache[type]=[dirruns,nodeIndex,diff];break}}else if(useCache&&(node=elem,outerCache=node[expando]||(node[expando]={}),uniqueCache=outerCache[node.uniqueID]||(outerCache[node.uniqueID]={}),cache=uniqueCache[type]||[],nodeIndex=cache[0]===dirruns&&cache[1],diff=nodeIndex),diff===!1)for(;(node=++nodeIndex&&node&&node[dir]||(diff=nodeIndex=0)||start.pop())&&((ofType?node.nodeName.toLowerCase()!==name:1!==node.nodeType)||!++diff||(useCache&&(outerCache=node[expando]||(node[expando]={}),uniqueCache=outerCache[node.uniqueID]||(outerCache[node.uniqueID]={}),uniqueCache[type]=[dirruns,diff]),node!==elem)););return diff-=last,diff===first||diff%first===0&&diff/first>=0}}},PSEUDO:function(pseudo,argument){var args,fn=Expr.pseudos[pseudo]||Expr.setFilters[pseudo.toLowerCase()]||Sizzle.error("unsupported pseudo: "+pseudo);return fn[expando]?fn(argument):fn.length>1?(args=[pseudo,pseudo,"",argument],Expr.setFilters.hasOwnProperty(pseudo.toLowerCase())?markFunction(function(seed,matches){for(var idx,matched=fn(seed,argument),i=matched.length;i--;)idx=indexOf(seed,matched[i]),seed[idx]=!(matches[idx]=matched[i])}):function(elem){return fn(elem,0,args)}):fn}},pseudos:{not:markFunction(function(selector){var input=[],results=[],matcher=compile(selector.replace(rtrim,"$1"));return matcher[expando]?markFunction(function(seed,matches,context,xml){for(var elem,unmatched=matcher(seed,null,xml,[]),i=seed.length;i--;)(elem=unmatched[i])&&(seed[i]=!(matches[i]=elem))}):function(elem,context,xml){return input[0]=elem,matcher(input,null,xml,results),input[0]=null,!results.pop()}}),has:markFunction(function(selector){return function(elem){return Sizzle(selector,elem).length>0}}),contains:markFunction(function(text){return text=text.replace(runescape,funescape),function(elem){return(elem.textContent||elem.innerText||getText(elem)).indexOf(text)>-1}}),lang:markFunction(function(lang){return ridentifier.test(lang||"")||Sizzle.error("unsupported lang: "+lang),lang=lang.replace(runescape,funescape).toLowerCase(),function(elem){var elemLang;do if(elemLang=documentIsHTML?elem.lang:elem.getAttribute("xml:lang")||elem.getAttribute("lang"))return elemLang=elemLang.toLowerCase(),elemLang===lang||0===elemLang.indexOf(lang+"-");while((elem=elem.parentNode)&&1===elem.nodeType);return!1}}),target:function(elem){var hash=window.location&&window.location.hash;return hash&&hash.slice(1)===elem.id},root:function(elem){return elem===docElem},focus:function(elem){return elem===document.activeElement&&(!document.hasFocus||document.hasFocus())&&!!(elem.type||elem.href||~elem.tabIndex)},enabled:createDisabledPseudo(!1),disabled:createDisabledPseudo(!0),checked:function(elem){var nodeName=elem.nodeName.toLowerCase();return"input"===nodeName&&!!elem.checked||"option"===nodeName&&!!elem.selected},selected:function(elem){return elem.parentNode&&elem.parentNode.selectedIndex,elem.selected===!0},empty:function(elem){for(elem=elem.firstChild;elem;elem=elem.nextSibling)if(elem.nodeType<6)return!1;return!0},parent:function(elem){return!Expr.pseudos.empty(elem)},header:function(elem){return rheader.test(elem.nodeName)},input:function(elem){return rinputs.test(elem.nodeName)},button:function(elem){var name=elem.nodeName.toLowerCase();return"input"===name&&"button"===elem.type||"button"===name},text:function(elem){var attr;return"input"===elem.nodeName.toLowerCase()&&"text"===elem.type&&(null==(attr=elem.getAttribute("type"))||"text"===attr.toLowerCase())},first:createPositionalPseudo(function(){return[0]}),last:createPositionalPseudo(function(matchIndexes,length){return[length-1]}),eq:createPositionalPseudo(function(matchIndexes,length,argument){return[argument<0?argument+length:argument]}),even:createPositionalPseudo(function(matchIndexes,length){for(var i=0;i=0;)matchIndexes.push(i);return matchIndexes}),gt:createPositionalPseudo(function(matchIndexes,length,argument){for(var i=argument<0?argument+length:argument;++i2&&"ID"===(token=tokens[0]).type&&support.getById&&9===context.nodeType&&documentIsHTML&&Expr.relative[tokens[1].type]){if(context=(Expr.find.ID(token.matches[0].replace(runescape,funescape),context)||[])[0],!context)return results;compiled&&(context=context.parentNode),selector=selector.slice(tokens.shift().value.length)}for(i=matchExpr.needsContext.test(selector)?0:tokens.length;i--&&(token=tokens[i],!Expr.relative[type=token.type]);)if((find=Expr.find[type])&&(seed=find(token.matches[0].replace(runescape,funescape),rsibling.test(tokens[0].type)&&testContext(context.parentNode)||context))){if(tokens.splice(i,1),selector=seed.length&&toSelector(tokens),!selector)return push.apply(results,seed),results;break}}return(compiled||compile(selector,match))(seed,context,!documentIsHTML,results,!context||rsibling.test(selector)&&testContext(context.parentNode)||context),results},support.sortStable=expando.split("").sort(sortOrder).join("")===expando,support.detectDuplicates=!!hasDuplicate,setDocument(),support.sortDetached=assert(function(el){return 1&el.compareDocumentPosition(document.createElement("fieldset"))}),assert(function(el){return el.innerHTML="","#"===el.firstChild.getAttribute("href")})||addHandle("type|href|height|width",function(elem,name,isXML){if(!isXML)return elem.getAttribute(name,"type"===name.toLowerCase()?1:2)}),support.attributes&&assert(function(el){return el.innerHTML="",el.firstChild.setAttribute("value",""),""===el.firstChild.getAttribute("value")})||addHandle("value",function(elem,name,isXML){if(!isXML&&"input"===elem.nodeName.toLowerCase())return elem.defaultValue}),assert(function(el){return null==el.getAttribute("disabled")})||addHandle(booleans,function(elem,name,isXML){var val;if(!isXML)return elem[name]===!0?name.toLowerCase():(val=elem.getAttributeNode(name))&&val.specified?val.value:null}),Sizzle}(window);jQuery.find=Sizzle,jQuery.expr=Sizzle.selectors,jQuery.expr[":"]=jQuery.expr.pseudos,jQuery.uniqueSort=jQuery.unique=Sizzle.uniqueSort,jQuery.text=Sizzle.getText,jQuery.isXMLDoc=Sizzle.isXML,jQuery.contains=Sizzle.contains,jQuery.escapeSelector=Sizzle.escape;var dir=function(elem,dir,until){for(var matched=[],truncate=void 0!==until;(elem=elem[dir])&&9!==elem.nodeType;)if(1===elem.nodeType){if(truncate&&jQuery(elem).is(until))break;matched.push(elem)}return matched},siblings=function(n,elem){for(var matched=[];n;n=n.nextSibling)1===n.nodeType&&n!==elem&&matched.push(n);return matched},rneedsContext=jQuery.expr.match.needsContext,rsingleTag=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,risSimple=/^.[^:#\[\.,]*$/;jQuery.filter=function(expr,elems,not){var elem=elems[0];return not&&(expr=":not("+expr+")"),1===elems.length&&1===elem.nodeType?jQuery.find.matchesSelector(elem,expr)?[elem]:[]:jQuery.find.matches(expr,jQuery.grep(elems,function(elem){return 1===elem.nodeType}))},jQuery.fn.extend({find:function(selector){var i,ret,len=this.length,self=this;if("string"!=typeof selector)return this.pushStack(jQuery(selector).filter(function(){for(i=0;i1?jQuery.uniqueSort(ret):ret},filter:function(selector){return this.pushStack(winnow(this,selector||[],!1))},not:function(selector){return this.pushStack(winnow(this,selector||[],!0))},is:function(selector){return!!winnow(this,"string"==typeof selector&&rneedsContext.test(selector)?jQuery(selector):selector||[],!1).length}});var rootjQuery,rquickExpr=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,init=jQuery.fn.init=function(selector,context,root){var match,elem;if(!selector)return this;if(root=root||rootjQuery,"string"==typeof selector){if(match="<"===selector[0]&&">"===selector[selector.length-1]&&selector.length>=3?[null,selector,null]:rquickExpr.exec(selector),!match||!match[1]&&context)return!context||context.jquery?(context||root).find(selector):this.constructor(context).find(selector);if(match[1]){if(context=context instanceof jQuery?context[0]:context,jQuery.merge(this,jQuery.parseHTML(match[1],context&&context.nodeType?context.ownerDocument||context:document,!0)),rsingleTag.test(match[1])&&jQuery.isPlainObject(context))for(match in context)jQuery.isFunction(this[match])?this[match](context[match]):this.attr(match,context[match]);return this}return elem=document.getElementById(match[2]),elem&&(this[0]=elem,this.length=1),this}return selector.nodeType?(this[0]=selector,this.length=1,this):jQuery.isFunction(selector)?void 0!==root.ready?root.ready(selector):selector(jQuery):jQuery.makeArray(selector,this)};init.prototype=jQuery.fn,rootjQuery=jQuery(document);var rparentsprev=/^(?:parents|prev(?:Until|All))/,guaranteedUnique={children:!0,contents:!0,next:!0,prev:!0};jQuery.fn.extend({has:function(target){var targets=jQuery(target,this),l=targets.length;return this.filter(function(){for(var i=0;i-1:1===cur.nodeType&&jQuery.find.matchesSelector(cur,selectors))){matched.push(cur);break}return this.pushStack(matched.length>1?jQuery.uniqueSort(matched):matched)},index:function(elem){return elem?"string"==typeof elem?indexOf.call(jQuery(elem),this[0]):indexOf.call(this,elem.jquery?elem[0]:elem):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(selector,context){return this.pushStack(jQuery.uniqueSort(jQuery.merge(this.get(),jQuery(selector,context))))},addBack:function(selector){return this.add(null==selector?this.prevObject:this.prevObject.filter(selector))}}),jQuery.each({parent:function(elem){var parent=elem.parentNode;return parent&&11!==parent.nodeType?parent:null},parents:function(elem){return dir(elem,"parentNode")},parentsUntil:function(elem,i,until){return dir(elem,"parentNode",until)},next:function(elem){return sibling(elem,"nextSibling")},prev:function(elem){return sibling(elem,"previousSibling")},nextAll:function(elem){return dir(elem,"nextSibling")},prevAll:function(elem){return dir(elem,"previousSibling")},nextUntil:function(elem,i,until){return dir(elem,"nextSibling",until)},prevUntil:function(elem,i,until){return dir(elem,"previousSibling",until)},siblings:function(elem){return siblings((elem.parentNode||{}).firstChild,elem)},children:function(elem){return siblings(elem.firstChild)},contents:function(elem){return elem.contentDocument||jQuery.merge([],elem.childNodes)}},function(name,fn){jQuery.fn[name]=function(until,selector){var matched=jQuery.map(this,fn,until);return"Until"!==name.slice(-5)&&(selector=until),selector&&"string"==typeof selector&&(matched=jQuery.filter(selector,matched)),this.length>1&&(guaranteedUnique[name]||jQuery.uniqueSort(matched),rparentsprev.test(name)&&matched.reverse()),this.pushStack(matched)}});var rnotwhite=/\S+/g;jQuery.Callbacks=function(options){options="string"==typeof options?createOptions(options):jQuery.extend({},options);var firing,memory,fired,locked,list=[],queue=[],firingIndex=-1,fire=function(){for(locked=options.once,fired=firing=!0;queue.length;firingIndex=-1)for(memory=queue.shift();++firingIndex-1;)list.splice(index,1),index<=firingIndex&&firingIndex--}),this},has:function(fn){return fn?jQuery.inArray(fn,list)>-1:list.length>0},empty:function(){return list&&(list=[]),this},disable:function(){return locked=queue=[],list=memory="",this},disabled:function(){return!list},lock:function(){return locked=queue=[],memory||firing||(list=memory=""),this},locked:function(){return!!locked},fireWith:function(context,args){return locked||(args=args||[],args=[context,args.slice?args.slice():args],queue.push(args),firing||fire()),this},fire:function(){return self.fireWith(this,arguments),this},fired:function(){return!!fired}};return self},jQuery.extend({Deferred:function(func){var tuples=[["notify","progress",jQuery.Callbacks("memory"),jQuery.Callbacks("memory"),2],["resolve","done",jQuery.Callbacks("once memory"),jQuery.Callbacks("once memory"),0,"resolved"],["reject","fail",jQuery.Callbacks("once memory"),jQuery.Callbacks("once memory"),1,"rejected"]],state="pending",promise={state:function(){return state},always:function(){return deferred.done(arguments).fail(arguments),this},"catch":function(fn){return promise.then(null,fn)},pipe:function(){var fns=arguments;return jQuery.Deferred(function(newDefer){ +jQuery.each(tuples,function(i,tuple){var fn=jQuery.isFunction(fns[tuple[4]])&&fns[tuple[4]];deferred[tuple[1]](function(){var returned=fn&&fn.apply(this,arguments);returned&&jQuery.isFunction(returned.promise)?returned.promise().progress(newDefer.notify).done(newDefer.resolve).fail(newDefer.reject):newDefer[tuple[0]+"With"](this,fn?[returned]:arguments)})}),fns=null}).promise()},then:function(onFulfilled,onRejected,onProgress){function resolve(depth,deferred,handler,special){return function(){var that=this,args=arguments,mightThrow=function(){var returned,then;if(!(depth=maxDepth&&(handler!==Thrower&&(that=void 0,args=[e]),deferred.rejectWith(that,args))}};depth?process():(jQuery.Deferred.getStackHook&&(process.stackTrace=jQuery.Deferred.getStackHook()),window.setTimeout(process))}}var maxDepth=0;return jQuery.Deferred(function(newDefer){tuples[0][3].add(resolve(0,newDefer,jQuery.isFunction(onProgress)?onProgress:Identity,newDefer.notifyWith)),tuples[1][3].add(resolve(0,newDefer,jQuery.isFunction(onFulfilled)?onFulfilled:Identity)),tuples[2][3].add(resolve(0,newDefer,jQuery.isFunction(onRejected)?onRejected:Thrower))}).promise()},promise:function(obj){return null!=obj?jQuery.extend(obj,promise):promise}},deferred={};return jQuery.each(tuples,function(i,tuple){var list=tuple[2],stateString=tuple[5];promise[tuple[1]]=list.add,stateString&&list.add(function(){state=stateString},tuples[3-i][2].disable,tuples[0][2].lock),list.add(tuple[3].fire),deferred[tuple[0]]=function(){return deferred[tuple[0]+"With"](this===deferred?void 0:this,arguments),this},deferred[tuple[0]+"With"]=list.fireWith}),promise.promise(deferred),func&&func.call(deferred,deferred),deferred},when:function(singleValue){var remaining=arguments.length,i=remaining,resolveContexts=Array(i),resolveValues=slice.call(arguments),master=jQuery.Deferred(),updateFunc=function(i){return function(value){resolveContexts[i]=this,resolveValues[i]=arguments.length>1?slice.call(arguments):value,--remaining||master.resolveWith(resolveContexts,resolveValues)}};if(remaining<=1&&(adoptValue(singleValue,master.done(updateFunc(i)).resolve,master.reject),"pending"===master.state()||jQuery.isFunction(resolveValues[i]&&resolveValues[i].then)))return master.then();for(;i--;)adoptValue(resolveValues[i],updateFunc(i),master.reject);return master.promise()}});var rerrorNames=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;jQuery.Deferred.exceptionHook=function(error,stack){window.console&&window.console.warn&&error&&rerrorNames.test(error.name)&&window.console.warn("jQuery.Deferred exception: "+error.message,error.stack,stack)},jQuery.readyException=function(error){window.setTimeout(function(){throw error})};var readyList=jQuery.Deferred();jQuery.fn.ready=function(fn){return readyList.then(fn)["catch"](function(error){jQuery.readyException(error)}),this},jQuery.extend({isReady:!1,readyWait:1,holdReady:function(hold){hold?jQuery.readyWait++:jQuery.ready(!0)},ready:function(wait){(wait===!0?--jQuery.readyWait:jQuery.isReady)||(jQuery.isReady=!0,wait!==!0&&--jQuery.readyWait>0||readyList.resolveWith(document,[jQuery]))}}),jQuery.ready.then=readyList.then,"complete"===document.readyState||"loading"!==document.readyState&&!document.documentElement.doScroll?window.setTimeout(jQuery.ready):(document.addEventListener("DOMContentLoaded",completed),window.addEventListener("load",completed));var access=function(elems,fn,key,value,chainable,emptyGet,raw){var i=0,len=elems.length,bulk=null==key;if("object"===jQuery.type(key)){chainable=!0;for(i in key)access(elems,fn,i,key[i],!0,emptyGet,raw)}else if(void 0!==value&&(chainable=!0,jQuery.isFunction(value)||(raw=!0),bulk&&(raw?(fn.call(elems,value),fn=null):(bulk=fn,fn=function(elem,key,value){return bulk.call(jQuery(elem),value)})),fn))for(;i1,null,!0)},removeData:function(key){return this.each(function(){dataUser.remove(this,key)})}}),jQuery.extend({queue:function(elem,type,data){var queue;if(elem)return type=(type||"fx")+"queue",queue=dataPriv.get(elem,type),data&&(!queue||jQuery.isArray(data)?queue=dataPriv.access(elem,type,jQuery.makeArray(data)):queue.push(data)),queue||[]},dequeue:function(elem,type){type=type||"fx";var queue=jQuery.queue(elem,type),startLength=queue.length,fn=queue.shift(),hooks=jQuery._queueHooks(elem,type),next=function(){jQuery.dequeue(elem,type)};"inprogress"===fn&&(fn=queue.shift(),startLength--),fn&&("fx"===type&&queue.unshift("inprogress"),delete hooks.stop,fn.call(elem,next,hooks)),!startLength&&hooks&&hooks.empty.fire()},_queueHooks:function(elem,type){var key=type+"queueHooks";return dataPriv.get(elem,key)||dataPriv.access(elem,key,{empty:jQuery.Callbacks("once memory").add(function(){dataPriv.remove(elem,[type+"queue",key])})})}}),jQuery.fn.extend({queue:function(type,data){var setter=2;return"string"!=typeof type&&(data=type,type="fx",setter--),arguments.length\x20\t\r\n\f]+)/i,rscriptType=/^$|\/(?:java|ecma)script/i,wrapMap={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};wrapMap.optgroup=wrapMap.option,wrapMap.tbody=wrapMap.tfoot=wrapMap.colgroup=wrapMap.caption=wrapMap.thead,wrapMap.th=wrapMap.td;var rhtml=/<|&#?\w+;/;!function(){var fragment=document.createDocumentFragment(),div=fragment.appendChild(document.createElement("div")),input=document.createElement("input");input.setAttribute("type","radio"),input.setAttribute("checked","checked"),input.setAttribute("name","t"),div.appendChild(input),support.checkClone=div.cloneNode(!0).cloneNode(!0).lastChild.checked,div.innerHTML="",support.noCloneChecked=!!div.cloneNode(!0).lastChild.defaultValue}();var documentElement=document.documentElement,rkeyEvent=/^key/,rmouseEvent=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,rtypenamespace=/^([^.]*)(?:\.(.+)|)/;jQuery.event={global:{},add:function(elem,types,handler,data,selector){var handleObjIn,eventHandle,tmp,events,t,handleObj,special,handlers,type,namespaces,origType,elemData=dataPriv.get(elem);if(elemData)for(handler.handler&&(handleObjIn=handler,handler=handleObjIn.handler,selector=handleObjIn.selector),selector&&jQuery.find.matchesSelector(documentElement,selector),handler.guid||(handler.guid=jQuery.guid++),(events=elemData.events)||(events=elemData.events={}),(eventHandle=elemData.handle)||(eventHandle=elemData.handle=function(e){return"undefined"!=typeof jQuery&&jQuery.event.triggered!==e.type?jQuery.event.dispatch.apply(elem,arguments):void 0}),types=(types||"").match(rnotwhite)||[""],t=types.length;t--;)tmp=rtypenamespace.exec(types[t])||[],type=origType=tmp[1],namespaces=(tmp[2]||"").split(".").sort(),type&&(special=jQuery.event.special[type]||{},type=(selector?special.delegateType:special.bindType)||type,special=jQuery.event.special[type]||{},handleObj=jQuery.extend({type:type,origType:origType,data:data,handler:handler,guid:handler.guid,selector:selector,needsContext:selector&&jQuery.expr.match.needsContext.test(selector),namespace:namespaces.join(".")},handleObjIn),(handlers=events[type])||(handlers=events[type]=[],handlers.delegateCount=0,special.setup&&special.setup.call(elem,data,namespaces,eventHandle)!==!1||elem.addEventListener&&elem.addEventListener(type,eventHandle)),special.add&&(special.add.call(elem,handleObj),handleObj.handler.guid||(handleObj.handler.guid=handler.guid)),selector?handlers.splice(handlers.delegateCount++,0,handleObj):handlers.push(handleObj),jQuery.event.global[type]=!0)},remove:function(elem,types,handler,selector,mappedTypes){var j,origCount,tmp,events,t,handleObj,special,handlers,type,namespaces,origType,elemData=dataPriv.hasData(elem)&&dataPriv.get(elem);if(elemData&&(events=elemData.events)){for(types=(types||"").match(rnotwhite)||[""],t=types.length;t--;)if(tmp=rtypenamespace.exec(types[t])||[],type=origType=tmp[1],namespaces=(tmp[2]||"").split(".").sort(),type){for(special=jQuery.event.special[type]||{},type=(selector?special.delegateType:special.bindType)||type,handlers=events[type]||[],tmp=tmp[2]&&new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)"),origCount=j=handlers.length;j--;)handleObj=handlers[j],!mappedTypes&&origType!==handleObj.origType||handler&&handler.guid!==handleObj.guid||tmp&&!tmp.test(handleObj.namespace)||selector&&selector!==handleObj.selector&&("**"!==selector||!handleObj.selector)||(handlers.splice(j,1),handleObj.selector&&handlers.delegateCount--,special.remove&&special.remove.call(elem,handleObj));origCount&&!handlers.length&&(special.teardown&&special.teardown.call(elem,namespaces,elemData.handle)!==!1||jQuery.removeEvent(elem,type,elemData.handle),delete events[type])}else for(type in events)jQuery.event.remove(elem,type+types[t],handler,selector,!0);jQuery.isEmptyObject(events)&&dataPriv.remove(elem,"handle events")}},dispatch:function(nativeEvent){var i,j,ret,matched,handleObj,handlerQueue,event=jQuery.event.fix(nativeEvent),args=new Array(arguments.length),handlers=(dataPriv.get(this,"events")||{})[event.type]||[],special=jQuery.event.special[event.type]||{};for(args[0]=event,i=1;i-1:jQuery.find(sel,this,null,[cur]).length),matches[sel]&&matches.push(handleObj);matches.length&&handlerQueue.push({elem:cur,handlers:matches})}return delegateCount\x20\t\r\n\f]*)[^>]*)\/>/gi,rnoInnerhtml=/\s*$/g;jQuery.extend({htmlPrefilter:function(html){return html.replace(rxhtmlTag,"<$1>")},clone:function(elem,dataAndEvents,deepDataAndEvents){var i,l,srcElements,destElements,clone=elem.cloneNode(!0),inPage=jQuery.contains(elem.ownerDocument,elem);if(!(support.noCloneChecked||1!==elem.nodeType&&11!==elem.nodeType||jQuery.isXMLDoc(elem)))for(destElements=getAll(clone),srcElements=getAll(elem),i=0,l=srcElements.length;i0&&setGlobalEval(destElements,!inPage&&getAll(elem,"script")),clone},cleanData:function(elems){for(var data,elem,type,special=jQuery.event.special,i=0;void 0!==(elem=elems[i]);i++)if(acceptData(elem)){if(data=elem[dataPriv.expando]){if(data.events)for(type in data.events)special[type]?jQuery.event.remove(elem,type):jQuery.removeEvent(elem,type,data.handle);elem[dataPriv.expando]=void 0}elem[dataUser.expando]&&(elem[dataUser.expando]=void 0)}}}),jQuery.fn.extend({detach:function(selector){return remove(this,selector,!0)},remove:function(selector){return remove(this,selector)},text:function(value){return access(this,function(value){return void 0===value?jQuery.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=value)})},null,value,arguments.length)},append:function(){return domManip(this,arguments,function(elem){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var target=manipulationTarget(this,elem);target.appendChild(elem)}})},prepend:function(){return domManip(this,arguments,function(elem){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var target=manipulationTarget(this,elem);target.insertBefore(elem,target.firstChild)}})},before:function(){return domManip(this,arguments,function(elem){this.parentNode&&this.parentNode.insertBefore(elem,this)})},after:function(){return domManip(this,arguments,function(elem){this.parentNode&&this.parentNode.insertBefore(elem,this.nextSibling)})},empty:function(){for(var elem,i=0;null!=(elem=this[i]);i++)1===elem.nodeType&&(jQuery.cleanData(getAll(elem,!1)),elem.textContent="");return this},clone:function(dataAndEvents,deepDataAndEvents){return dataAndEvents=null!=dataAndEvents&&dataAndEvents,deepDataAndEvents=null==deepDataAndEvents?dataAndEvents:deepDataAndEvents,this.map(function(){return jQuery.clone(this,dataAndEvents,deepDataAndEvents)})},html:function(value){return access(this,function(value){var elem=this[0]||{},i=0,l=this.length;if(void 0===value&&1===elem.nodeType)return elem.innerHTML;if("string"==typeof value&&!rnoInnerhtml.test(value)&&!wrapMap[(rtagName.exec(value)||["",""])[1].toLowerCase()]){value=jQuery.htmlPrefilter(value);try{for(;i1)}}),jQuery.Tween=Tween,Tween.prototype={constructor:Tween,init:function(elem,options,prop,end,easing,unit){this.elem=elem,this.prop=prop,this.easing=easing||jQuery.easing._default,this.options=options,this.start=this.now=this.cur(),this.end=end,this.unit=unit||(jQuery.cssNumber[prop]?"":"px")},cur:function(){var hooks=Tween.propHooks[this.prop];return hooks&&hooks.get?hooks.get(this):Tween.propHooks._default.get(this)},run:function(percent){var eased,hooks=Tween.propHooks[this.prop];return this.options.duration?this.pos=eased=jQuery.easing[this.easing](percent,this.options.duration*percent,0,1,this.options.duration):this.pos=eased=percent,this.now=(this.end-this.start)*eased+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),hooks&&hooks.set?hooks.set(this):Tween.propHooks._default.set(this),this}},Tween.prototype.init.prototype=Tween.prototype,Tween.propHooks={_default:{get:function(tween){var result;return 1!==tween.elem.nodeType||null!=tween.elem[tween.prop]&&null==tween.elem.style[tween.prop]?tween.elem[tween.prop]:(result=jQuery.css(tween.elem,tween.prop,""),result&&"auto"!==result?result:0)},set:function(tween){jQuery.fx.step[tween.prop]?jQuery.fx.step[tween.prop](tween):1!==tween.elem.nodeType||null==tween.elem.style[jQuery.cssProps[tween.prop]]&&!jQuery.cssHooks[tween.prop]?tween.elem[tween.prop]=tween.now:jQuery.style(tween.elem,tween.prop,tween.now+tween.unit)}}},Tween.propHooks.scrollTop=Tween.propHooks.scrollLeft={set:function(tween){tween.elem.nodeType&&tween.elem.parentNode&&(tween.elem[tween.prop]=tween.now)}},jQuery.easing={linear:function(p){return p},swing:function(p){return.5-Math.cos(p*Math.PI)/2},_default:"swing"},jQuery.fx=Tween.prototype.init,jQuery.fx.step={};var fxNow,timerId,rfxtypes=/^(?:toggle|show|hide)$/,rrun=/queueHooks$/;jQuery.Animation=jQuery.extend(Animation,{tweeners:{"*":[function(prop,value){var tween=this.createTween(prop,value);return adjustCSS(tween.elem,prop,rcssNum.exec(value),tween),tween}]},tweener:function(props,callback){jQuery.isFunction(props)?(callback=props,props=["*"]):props=props.match(rnotwhite);for(var prop,index=0,length=props.length;index1)},removeAttr:function(name){return this.each(function(){jQuery.removeAttr(this,name)})}}),jQuery.extend({attr:function(elem,name,value){var ret,hooks,nType=elem.nodeType;if(3!==nType&&8!==nType&&2!==nType)return"undefined"==typeof elem.getAttribute?jQuery.prop(elem,name,value):(1===nType&&jQuery.isXMLDoc(elem)||(hooks=jQuery.attrHooks[name.toLowerCase()]||(jQuery.expr.match.bool.test(name)?boolHook:void 0)),void 0!==value?null===value?void jQuery.removeAttr(elem,name):hooks&&"set"in hooks&&void 0!==(ret=hooks.set(elem,value,name))?ret:(elem.setAttribute(name,value+""),value):hooks&&"get"in hooks&&null!==(ret=hooks.get(elem,name))?ret:(ret=jQuery.find.attr(elem,name),null==ret?void 0:ret))},attrHooks:{type:{set:function(elem,value){if(!support.radioValue&&"radio"===value&&jQuery.nodeName(elem,"input")){var val=elem.value;return elem.setAttribute("type",value),val&&(elem.value=val),value}}}},removeAttr:function(elem,value){var name,i=0,attrNames=value&&value.match(rnotwhite);if(attrNames&&1===elem.nodeType)for(;name=attrNames[i++];)elem.removeAttribute(name)}}),boolHook={set:function(elem,value,name){return value===!1?jQuery.removeAttr(elem,name):elem.setAttribute(name,name),name}},jQuery.each(jQuery.expr.match.bool.source.match(/\w+/g),function(i,name){var getter=attrHandle[name]||jQuery.find.attr;attrHandle[name]=function(elem,name,isXML){var ret,handle,lowercaseName=name.toLowerCase();return isXML||(handle=attrHandle[lowercaseName],attrHandle[lowercaseName]=ret,ret=null!=getter(elem,name,isXML)?lowercaseName:null,attrHandle[lowercaseName]=handle),ret}});var rfocusable=/^(?:input|select|textarea|button)$/i,rclickable=/^(?:a|area)$/i;jQuery.fn.extend({prop:function(name,value){return access(this,jQuery.prop,name,value,arguments.length>1)},removeProp:function(name){return this.each(function(){delete this[jQuery.propFix[name]||name]})}}),jQuery.extend({prop:function(elem,name,value){var ret,hooks,nType=elem.nodeType;if(3!==nType&&8!==nType&&2!==nType)return 1===nType&&jQuery.isXMLDoc(elem)||(name=jQuery.propFix[name]||name,hooks=jQuery.propHooks[name]),void 0!==value?hooks&&"set"in hooks&&void 0!==(ret=hooks.set(elem,value,name))?ret:elem[name]=value:hooks&&"get"in hooks&&null!==(ret=hooks.get(elem,name))?ret:elem[name]},propHooks:{tabIndex:{get:function(elem){var tabindex=jQuery.find.attr(elem,"tabindex");return tabindex?parseInt(tabindex,10):rfocusable.test(elem.nodeName)||rclickable.test(elem.nodeName)&&elem.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),support.optSelected||(jQuery.propHooks.selected={get:function(elem){var parent=elem.parentNode;return parent&&parent.parentNode&&parent.parentNode.selectedIndex,null},set:function(elem){var parent=elem.parentNode;parent&&(parent.selectedIndex,parent.parentNode&&parent.parentNode.selectedIndex)}}),jQuery.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){jQuery.propFix[this.toLowerCase()]=this});var rclass=/[\t\r\n\f]/g;jQuery.fn.extend({addClass:function(value){var classes,elem,cur,curValue,clazz,j,finalValue,i=0;if(jQuery.isFunction(value))return this.each(function(j){jQuery(this).addClass(value.call(this,j,getClass(this)))});if("string"==typeof value&&value)for(classes=value.match(rnotwhite)||[];elem=this[i++];)if(curValue=getClass(elem),cur=1===elem.nodeType&&(" "+curValue+" ").replace(rclass," ")){for(j=0;clazz=classes[j++];)cur.indexOf(" "+clazz+" ")<0&&(cur+=clazz+" ");finalValue=jQuery.trim(cur),curValue!==finalValue&&elem.setAttribute("class",finalValue)}return this},removeClass:function(value){var classes,elem,cur,curValue,clazz,j,finalValue,i=0;if(jQuery.isFunction(value))return this.each(function(j){jQuery(this).removeClass(value.call(this,j,getClass(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof value&&value)for(classes=value.match(rnotwhite)||[];elem=this[i++];)if(curValue=getClass(elem),cur=1===elem.nodeType&&(" "+curValue+" ").replace(rclass," ")){for(j=0;clazz=classes[j++];)for(;cur.indexOf(" "+clazz+" ")>-1;)cur=cur.replace(" "+clazz+" "," ");finalValue=jQuery.trim(cur),curValue!==finalValue&&elem.setAttribute("class",finalValue)}return this},toggleClass:function(value,stateVal){var type=typeof value;return"boolean"==typeof stateVal&&"string"===type?stateVal?this.addClass(value):this.removeClass(value):jQuery.isFunction(value)?this.each(function(i){jQuery(this).toggleClass(value.call(this,i,getClass(this),stateVal),stateVal)}):this.each(function(){var className,i,self,classNames;if("string"===type)for(i=0,self=jQuery(this),classNames=value.match(rnotwhite)||[];className=classNames[i++];)self.hasClass(className)?self.removeClass(className):self.addClass(className);else void 0!==value&&"boolean"!==type||(className=getClass(this),className&&dataPriv.set(this,"__className__",className),this.setAttribute&&this.setAttribute("class",className||value===!1?"":dataPriv.get(this,"__className__")||""))})},hasClass:function(selector){var className,elem,i=0;for(className=" "+selector+" ";elem=this[i++];)if(1===elem.nodeType&&(" "+getClass(elem)+" ").replace(rclass," ").indexOf(className)>-1)return!0;return!1}});var rreturn=/\r/g,rspaces=/[\x20\t\r\n\f]+/g;jQuery.fn.extend({val:function(value){var hooks,ret,isFunction,elem=this[0];{if(arguments.length)return isFunction=jQuery.isFunction(value),this.each(function(i){var val;1===this.nodeType&&(val=isFunction?value.call(this,i,jQuery(this).val()):value,null==val?val="":"number"==typeof val?val+="":jQuery.isArray(val)&&(val=jQuery.map(val,function(value){return null==value?"":value+""})),hooks=jQuery.valHooks[this.type]||jQuery.valHooks[this.nodeName.toLowerCase()],hooks&&"set"in hooks&&void 0!==hooks.set(this,val,"value")||(this.value=val))});if(elem)return hooks=jQuery.valHooks[elem.type]||jQuery.valHooks[elem.nodeName.toLowerCase()],hooks&&"get"in hooks&&void 0!==(ret=hooks.get(elem,"value"))?ret:(ret=elem.value,"string"==typeof ret?ret.replace(rreturn,""):null==ret?"":ret)}}}),jQuery.extend({valHooks:{option:{get:function(elem){var val=jQuery.find.attr(elem,"value");return null!=val?val:jQuery.trim(jQuery.text(elem)).replace(rspaces," ")}},select:{get:function(elem){for(var value,option,options=elem.options,index=elem.selectedIndex,one="select-one"===elem.type,values=one?null:[],max=one?index+1:options.length,i=index<0?max:one?index:0;i-1)&&(optionSet=!0);return optionSet||(elem.selectedIndex=-1),values}}}}),jQuery.each(["radio","checkbox"],function(){jQuery.valHooks[this]={set:function(elem,value){if(jQuery.isArray(value))return elem.checked=jQuery.inArray(jQuery(elem).val(),value)>-1}},support.checkOn||(jQuery.valHooks[this].get=function(elem){return null===elem.getAttribute("value")?"on":elem.value})});var rfocusMorph=/^(?:focusinfocus|focusoutblur)$/;jQuery.extend(jQuery.event,{trigger:function(event,data,elem,onlyHandlers){var i,cur,tmp,bubbleType,ontype,handle,special,eventPath=[elem||document],type=hasOwn.call(event,"type")?event.type:event,namespaces=hasOwn.call(event,"namespace")?event.namespace.split("."):[];if(cur=tmp=elem=elem||document,3!==elem.nodeType&&8!==elem.nodeType&&!rfocusMorph.test(type+jQuery.event.triggered)&&(type.indexOf(".")>-1&&(namespaces=type.split("."),type=namespaces.shift(),namespaces.sort()),ontype=type.indexOf(":")<0&&"on"+type,event=event[jQuery.expando]?event:new jQuery.Event(type,"object"==typeof event&&event),event.isTrigger=onlyHandlers?2:3,event.namespace=namespaces.join("."),event.rnamespace=event.namespace?new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,event.result=void 0,event.target||(event.target=elem),data=null==data?[event]:jQuery.makeArray(data,[event]),special=jQuery.event.special[type]||{},onlyHandlers||!special.trigger||special.trigger.apply(elem,data)!==!1)){if(!onlyHandlers&&!special.noBubble&&!jQuery.isWindow(elem)){for(bubbleType=special.delegateType||type,rfocusMorph.test(bubbleType+type)||(cur=cur.parentNode);cur;cur=cur.parentNode)eventPath.push(cur),tmp=cur;tmp===(elem.ownerDocument||document)&&eventPath.push(tmp.defaultView||tmp.parentWindow||window)}for(i=0;(cur=eventPath[i++])&&!event.isPropagationStopped();)event.type=i>1?bubbleType:special.bindType||type,handle=(dataPriv.get(cur,"events")||{})[event.type]&&dataPriv.get(cur,"handle"),handle&&handle.apply(cur,data),handle=ontype&&cur[ontype],handle&&handle.apply&&acceptData(cur)&&(event.result=handle.apply(cur,data),event.result===!1&&event.preventDefault());return event.type=type,onlyHandlers||event.isDefaultPrevented()||special._default&&special._default.apply(eventPath.pop(),data)!==!1||!acceptData(elem)||ontype&&jQuery.isFunction(elem[type])&&!jQuery.isWindow(elem)&&(tmp=elem[ontype],tmp&&(elem[ontype]=null),jQuery.event.triggered=type,elem[type](),jQuery.event.triggered=void 0,tmp&&(elem[ontype]=tmp)),event.result}},simulate:function(type,elem,event){var e=jQuery.extend(new jQuery.Event,event,{type:type,isSimulated:!0});jQuery.event.trigger(e,null,elem)}}),jQuery.fn.extend({trigger:function(type,data){return this.each(function(){jQuery.event.trigger(type,data,this)})},triggerHandler:function(type,data){var elem=this[0];if(elem)return jQuery.event.trigger(type,data,elem,!0)}}),jQuery.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(i,name){jQuery.fn[name]=function(data,fn){return arguments.length>0?this.on(name,null,data,fn):this.trigger(name)}}),jQuery.fn.extend({hover:function(fnOver,fnOut){return this.mouseenter(fnOver).mouseleave(fnOut||fnOver)}}),support.focusin="onfocusin"in window,support.focusin||jQuery.each({focus:"focusin",blur:"focusout"},function(orig,fix){var handler=function(event){jQuery.event.simulate(fix,event.target,jQuery.event.fix(event))};jQuery.event.special[fix]={setup:function(){var doc=this.ownerDocument||this,attaches=dataPriv.access(doc,fix);attaches||doc.addEventListener(orig,handler,!0),dataPriv.access(doc,fix,(attaches||0)+1)},teardown:function(){var doc=this.ownerDocument||this,attaches=dataPriv.access(doc,fix)-1;attaches?dataPriv.access(doc,fix,attaches):(doc.removeEventListener(orig,handler,!0),dataPriv.remove(doc,fix))}}});var location=window.location,nonce=jQuery.now(),rquery=/\?/;jQuery.parseXML=function(data){var xml;if(!data||"string"!=typeof data)return null;try{xml=(new window.DOMParser).parseFromString(data,"text/xml")}catch(e){xml=void 0}return xml&&!xml.getElementsByTagName("parsererror").length||jQuery.error("Invalid XML: "+data),xml};var rbracket=/\[\]$/,rCRLF=/\r?\n/g,rsubmitterTypes=/^(?:submit|button|image|reset|file)$/i,rsubmittable=/^(?:input|select|textarea|keygen)/i;jQuery.param=function(a,traditional){var prefix,s=[],add=function(key,valueOrFunction){var value=jQuery.isFunction(valueOrFunction)?valueOrFunction():valueOrFunction;s[s.length]=encodeURIComponent(key)+"="+encodeURIComponent(null==value?"":value)};if(jQuery.isArray(a)||a.jquery&&!jQuery.isPlainObject(a))jQuery.each(a,function(){add(this.name,this.value)});else for(prefix in a)buildParams(prefix,a[prefix],traditional,add);return s.join("&")},jQuery.fn.extend({serialize:function(){return jQuery.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var elements=jQuery.prop(this,"elements");return elements?jQuery.makeArray(elements):this}).filter(function(){var type=this.type;return this.name&&!jQuery(this).is(":disabled")&&rsubmittable.test(this.nodeName)&&!rsubmitterTypes.test(type)&&(this.checked||!rcheckableType.test(type))}).map(function(i,elem){var val=jQuery(this).val();return null==val?null:jQuery.isArray(val)?jQuery.map(val,function(val){return{name:elem.name,value:val.replace(rCRLF,"\r\n")}}):{name:elem.name,value:val.replace(rCRLF,"\r\n")}}).get()}});var r20=/%20/g,rhash=/#.*$/,rts=/([?&])_=[^&]*/,rheaders=/^(.*?):[ \t]*([^\r\n]*)$/gm,rlocalProtocol=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,rnoContent=/^(?:GET|HEAD)$/,rprotocol=/^\/\//,prefilters={},transports={},allTypes="*/".concat("*"),originAnchor=document.createElement("a");originAnchor.href=location.href,jQuery.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:location.href,type:"GET",isLocal:rlocalProtocol.test(location.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":allTypes,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":jQuery.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(target,settings){return settings?ajaxExtend(ajaxExtend(target,jQuery.ajaxSettings),settings):ajaxExtend(jQuery.ajaxSettings,target)},ajaxPrefilter:addToPrefiltersOrTransports(prefilters),ajaxTransport:addToPrefiltersOrTransports(transports),ajax:function(url,options){function done(status,nativeStatusText,responses,headers){var isSuccess,success,error,response,modified,statusText=nativeStatusText;completed||(completed=!0,timeoutTimer&&window.clearTimeout(timeoutTimer),transport=void 0,responseHeadersString=headers||"",jqXHR.readyState=status>0?4:0,isSuccess=status>=200&&status<300||304===status,responses&&(response=ajaxHandleResponses(s,jqXHR,responses)),response=ajaxConvert(s,response,jqXHR,isSuccess),isSuccess?(s.ifModified&&(modified=jqXHR.getResponseHeader("Last-Modified"),modified&&(jQuery.lastModified[cacheURL]=modified),modified=jqXHR.getResponseHeader("etag"),modified&&(jQuery.etag[cacheURL]=modified)),204===status||"HEAD"===s.type?statusText="nocontent":304===status?statusText="notmodified":(statusText=response.state,success=response.data,error=response.error,isSuccess=!error)):(error=statusText,!status&&statusText||(statusText="error",status<0&&(status=0))),jqXHR.status=status,jqXHR.statusText=(nativeStatusText||statusText)+"",isSuccess?deferred.resolveWith(callbackContext,[success,statusText,jqXHR]):deferred.rejectWith(callbackContext,[jqXHR,statusText,error]),jqXHR.statusCode(statusCode),statusCode=void 0,fireGlobals&&globalEventContext.trigger(isSuccess?"ajaxSuccess":"ajaxError",[jqXHR,s,isSuccess?success:error]),completeDeferred.fireWith(callbackContext,[jqXHR,statusText]),fireGlobals&&(globalEventContext.trigger("ajaxComplete",[jqXHR,s]),--jQuery.active||jQuery.event.trigger("ajaxStop")))}"object"==typeof url&&(options=url,url=void 0),options=options||{};var transport,cacheURL,responseHeadersString,responseHeaders,timeoutTimer,urlAnchor,completed,fireGlobals,i,uncached,s=jQuery.ajaxSetup({},options),callbackContext=s.context||s,globalEventContext=s.context&&(callbackContext.nodeType||callbackContext.jquery)?jQuery(callbackContext):jQuery.event,deferred=jQuery.Deferred(),completeDeferred=jQuery.Callbacks("once memory"),statusCode=s.statusCode||{},requestHeaders={},requestHeadersNames={},strAbort="canceled",jqXHR={readyState:0,getResponseHeader:function(key){var match;if(completed){if(!responseHeaders)for(responseHeaders={};match=rheaders.exec(responseHeadersString);)responseHeaders[match[1].toLowerCase()]=match[2];match=responseHeaders[key.toLowerCase()]}return null==match?null:match},getAllResponseHeaders:function(){return completed?responseHeadersString:null},setRequestHeader:function(name,value){return null==completed&&(name=requestHeadersNames[name.toLowerCase()]=requestHeadersNames[name.toLowerCase()]||name,requestHeaders[name]=value),this},overrideMimeType:function(type){return null==completed&&(s.mimeType=type),this},statusCode:function(map){var code;if(map)if(completed)jqXHR.always(map[jqXHR.status]);else for(code in map)statusCode[code]=[statusCode[code],map[code]];return this},abort:function(statusText){var finalText=statusText||strAbort;return transport&&transport.abort(finalText),done(0,finalText),this}};if(deferred.promise(jqXHR),s.url=((url||s.url||location.href)+"").replace(rprotocol,location.protocol+"//"),s.type=options.method||options.type||s.method||s.type,s.dataTypes=(s.dataType||"*").toLowerCase().match(rnotwhite)||[""],null==s.crossDomain){urlAnchor=document.createElement("a");try{urlAnchor.href=s.url,urlAnchor.href=urlAnchor.href,s.crossDomain=originAnchor.protocol+"//"+originAnchor.host!=urlAnchor.protocol+"//"+urlAnchor.host}catch(e){s.crossDomain=!0}}if(s.data&&s.processData&&"string"!=typeof s.data&&(s.data=jQuery.param(s.data,s.traditional)),inspectPrefiltersOrTransports(prefilters,s,options,jqXHR),completed)return jqXHR;fireGlobals=jQuery.event&&s.global,fireGlobals&&0===jQuery.active++&&jQuery.event.trigger("ajaxStart"),s.type=s.type.toUpperCase(),s.hasContent=!rnoContent.test(s.type),cacheURL=s.url.replace(rhash,""),s.hasContent?s.data&&s.processData&&0===(s.contentType||"").indexOf("application/x-www-form-urlencoded")&&(s.data=s.data.replace(r20,"+")):(uncached=s.url.slice(cacheURL.length),s.data&&(cacheURL+=(rquery.test(cacheURL)?"&":"?")+s.data,delete s.data),s.cache===!1&&(cacheURL=cacheURL.replace(rts,""),uncached=(rquery.test(cacheURL)?"&":"?")+"_="+nonce++ +uncached),s.url=cacheURL+uncached),s.ifModified&&(jQuery.lastModified[cacheURL]&&jqXHR.setRequestHeader("If-Modified-Since",jQuery.lastModified[cacheURL]),jQuery.etag[cacheURL]&&jqXHR.setRequestHeader("If-None-Match",jQuery.etag[cacheURL])),(s.data&&s.hasContent&&s.contentType!==!1||options.contentType)&&jqXHR.setRequestHeader("Content-Type",s.contentType),jqXHR.setRequestHeader("Accept",s.dataTypes[0]&&s.accepts[s.dataTypes[0]]?s.accepts[s.dataTypes[0]]+("*"!==s.dataTypes[0]?", "+allTypes+"; q=0.01":""):s.accepts["*"]);for(i in s.headers)jqXHR.setRequestHeader(i,s.headers[i]);if(s.beforeSend&&(s.beforeSend.call(callbackContext,jqXHR,s)===!1||completed))return jqXHR.abort();if(strAbort="abort",completeDeferred.add(s.complete),jqXHR.done(s.success),jqXHR.fail(s.error),transport=inspectPrefiltersOrTransports(transports,s,options,jqXHR)){if(jqXHR.readyState=1,fireGlobals&&globalEventContext.trigger("ajaxSend",[jqXHR,s]),completed)return jqXHR;s.async&&s.timeout>0&&(timeoutTimer=window.setTimeout(function(){jqXHR.abort("timeout")},s.timeout));try{completed=!1,transport.send(requestHeaders,done)}catch(e){if(completed)throw e;done(-1,e)}}else done(-1,"No Transport");return jqXHR},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json")},getScript:function(url,callback){return jQuery.get(url,void 0,callback,"script")}}),jQuery.each(["get","post"],function(i,method){jQuery[method]=function(url,data,callback,type){return jQuery.isFunction(data)&&(type=type||callback,callback=data,data=void 0),jQuery.ajax(jQuery.extend({url:url,type:method,dataType:type,data:data,success:callback},jQuery.isPlainObject(url)&&url))}}),jQuery._evalUrl=function(url){return jQuery.ajax({url:url,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},jQuery.fn.extend({wrapAll:function(html){var wrap;return this[0]&&(jQuery.isFunction(html)&&(html=html.call(this[0])),wrap=jQuery(html,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&wrap.insertBefore(this[0]),wrap.map(function(){for(var elem=this;elem.firstElementChild;)elem=elem.firstElementChild;return elem}).append(this)),this},wrapInner:function(html){return jQuery.isFunction(html)?this.each(function(i){jQuery(this).wrapInner(html.call(this,i))}):this.each(function(){var self=jQuery(this),contents=self.contents();contents.length?contents.wrapAll(html):self.append(html)})},wrap:function(html){var isFunction=jQuery.isFunction(html);return this.each(function(i){jQuery(this).wrapAll(isFunction?html.call(this,i):html)})},unwrap:function(selector){return this.parent(selector).not("body").each(function(){jQuery(this).replaceWith(this.childNodes)}),this}}),jQuery.expr.pseudos.hidden=function(elem){return!jQuery.expr.pseudos.visible(elem)},jQuery.expr.pseudos.visible=function(elem){return!!(elem.offsetWidth||elem.offsetHeight||elem.getClientRects().length)},jQuery.ajaxSettings.xhr=function(){try{return new window.XMLHttpRequest}catch(e){}};var xhrSuccessStatus={0:200,1223:204},xhrSupported=jQuery.ajaxSettings.xhr();support.cors=!!xhrSupported&&"withCredentials"in xhrSupported,support.ajax=xhrSupported=!!xhrSupported,jQuery.ajaxTransport(function(options){var callback,errorCallback;if(support.cors||xhrSupported&&!options.crossDomain)return{send:function(headers,complete){var i,xhr=options.xhr();if(xhr.open(options.type,options.url,options.async,options.username,options.password),options.xhrFields)for(i in options.xhrFields)xhr[i]=options.xhrFields[i];options.mimeType&&xhr.overrideMimeType&&xhr.overrideMimeType(options.mimeType),options.crossDomain||headers["X-Requested-With"]||(headers["X-Requested-With"]="XMLHttpRequest");for(i in headers)xhr.setRequestHeader(i,headers[i]);callback=function(type){return function(){callback&&(callback=errorCallback=xhr.onload=xhr.onerror=xhr.onabort=xhr.onreadystatechange=null,"abort"===type?xhr.abort():"error"===type?"number"!=typeof xhr.status?complete(0,"error"):complete(xhr.status,xhr.statusText):complete(xhrSuccessStatus[xhr.status]||xhr.status,xhr.statusText,"text"!==(xhr.responseType||"text")||"string"!=typeof xhr.responseText?{binary:xhr.response}:{text:xhr.responseText},xhr.getAllResponseHeaders()))}},xhr.onload=callback(),errorCallback=xhr.onerror=callback("error"),void 0!==xhr.onabort?xhr.onabort=errorCallback:xhr.onreadystatechange=function(){4===xhr.readyState&&window.setTimeout(function(){callback&&errorCallback()})},callback=callback("abort");try{xhr.send(options.hasContent&&options.data||null)}catch(e){if(callback)throw e}},abort:function(){callback&&callback()}}}),jQuery.ajaxPrefilter(function(s){s.crossDomain&&(s.contents.script=!1)}),jQuery.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(text){return jQuery.globalEval(text),text}}}),jQuery.ajaxPrefilter("script",function(s){void 0===s.cache&&(s.cache=!1),s.crossDomain&&(s.type="GET")}),jQuery.ajaxTransport("script",function(s){if(s.crossDomain){var script,callback;return{send:function(_,complete){script=jQuery(" + + + + + +
+
+
+ + + diff --git a/lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-nativeHTML.html b/lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-nativeHTML.html new file mode 100644 index 0000000..9323eb7 --- /dev/null +++ b/lib/infusion/src/framework/preferences/html/SeparatedPanelPrefsEditorFrame-nativeHTML.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + Preferences Editor + + + +
+
+
+ + + diff --git a/lib/infusion/src/framework/preferences/images/default/separatedpanelbg.png b/lib/infusion/src/framework/preferences/images/default/separatedpanelbg.png new file mode 100644 index 0000000..8ec84b2 Binary files /dev/null and b/lib/infusion/src/framework/preferences/images/default/separatedpanelbg.png differ diff --git a/lib/infusion/src/framework/preferences/js/AuxBuilder.js b/lib/infusion/src/framework/preferences/js/AuxBuilder.js new file mode 100644 index 0000000..9b30755 --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/Builder.js b/lib/infusion/src/framework/preferences/js/Builder.js new file mode 100644 index 0000000..c47eec7 --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/Enactors.js b/lib/infusion/src/framework/preferences/js/Enactors.js new file mode 100644 index 0000000..3defda4 --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/FullNoPreviewPrefsEditor.js b/lib/infusion/src/framework/preferences/js/FullNoPreviewPrefsEditor.js new file mode 100644 index 0000000..f6ace02 --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/FullPreviewPrefsEditor.js b/lib/infusion/src/framework/preferences/js/FullPreviewPrefsEditor.js new file mode 100644 index 0000000..1d9475e --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/Panels.js b/lib/infusion/src/framework/preferences/js/Panels.js new file mode 100644 index 0000000..f789112 --- /dev/null +++ b/lib/infusion/src/framework/preferences/js/Panels.js @@ -0,0 +1,1034 @@ +/* +Copyright 2013-2016 OCAD University +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"; + + /********************** + * msgLookup grade * + **********************/ + + fluid.defaults("fluid.prefs.msgLookup", { + gradeNames: ["fluid.component"], + members: { + msgLookup: { + expander: { + funcName: "fluid.prefs.stringLookup", + args: ["{msgResolver}", "{that}.options.stringArrayIndex"] + } + } + }, + stringArrayIndex: {} + }); + + fluid.prefs.stringLookup = function (messageResolver, stringArrayIndex) { + var that = {id: fluid.allocateGuid()}; + that.singleLookup = function (value) { + var looked = messageResolver.lookup([value]); + return fluid.get(looked, "template"); + }; + that.multiLookup = function (values) { + return fluid.transform(values, function (value) { + return that.singleLookup(value); + }); + }; + that.lookup = function (value) { + var values = fluid.get(stringArrayIndex, value) || value; + var lookupFn = fluid.isArrayable(values) ? "multiLookup" : "singleLookup"; + return that[lookupFn](values); + }; + that.resolvePathSegment = that.lookup; + return that; + }; + + /*********************************************** + * Base grade panel + ***********************************************/ + + fluid.defaults("fluid.prefs.panel", { + gradeNames: ["fluid.prefs.msgLookup", "fluid.rendererComponent"], + events: { + onDomBind: null + }, + // Any listener that requires a DOM element, should be registered + // to the onDomBind listener. By default it is fired by onCreate, but + // when used as a subpanel, it will be triggered by the resetDomBinder invoker. + listeners: { + "onCreate.onDomBind": "{that}.events.onDomBind" + }, + components: { + msgResolver: { + type: "fluid.messageResolver" + } + }, + rendererOptions: { + messageLocator: "{msgResolver}.resolve" + }, + distributeOptions: { + source: "{that}.options.messageBase", + target: "{that > msgResolver}.options.messageBase" + } + }); + + /*************************** + * Base grade for subpanel * + ***************************/ + + fluid.defaults("fluid.prefs.subPanel", { + gradeNames: ["fluid.prefs.panel", "{that}.getDomBindGrade"], + listeners: { + "{compositePanel}.events.afterRender": { + listener: "{that}.events.afterRender", + args: ["{that}"] + }, + // Changing the firing of onDomBind from the onCreate. + // This is due to the fact that the rendering process, controlled by the + // composite panel, will set/replace the DOM elements. + "onCreate.onDomBind": null, // remove listener + "afterRender.onDomBind": "{that}.resetDomBinder" + }, + rules: { + expander: { + func: "fluid.prefs.subPanel.generateRules", + args: ["{that}.options.preferenceMap"] + } + }, + invokers: { + refreshView: "{compositePanel}.refreshView", + // resetDomBinder must fire the onDomBind event + resetDomBinder: { + funcName: "fluid.prefs.subPanel.resetDomBinder", + args: ["{that}"] + }, + getDomBindGrade: { + funcName: "fluid.prefs.subPanel.getDomBindGrade", + args: ["{prefsEditor}"] + } + }, + strings: {}, + parentBundle: "{compositePanel}.messageResolver", + renderOnInit: false + }); + + fluid.defaults("fluid.prefs.subPanel.domBind", { + gradeNames: ["fluid.component"], + listeners: { + "onDomBind.domChange": { + listener: "{prefsEditor}.events.onSignificantDOMChange" + } + } + }); + + fluid.prefs.subPanel.getDomBindGrade = function (prefsEditor) { + var hasListener = fluid.get(prefsEditor, "options.events.onSignificantDOMChange") !== undefined; + if (hasListener) { + return "fluid.prefs.subPanel.domBind"; + } + }; + + /* + * Since the composite panel manages the rendering of the subpanels + * the markup used by subpanels needs to be completely replaced. + * The subpanel's container is refereshed to point at the newly + * rendered markup, and the domBinder is re-initialized. Once + * this is all done, the onDomBind event is fired. + */ + fluid.prefs.subPanel.resetDomBinder = function (that) { + // TODO: The line below to find the container jQuery instance was copied from the framework code - + // https://github.com/fluid-project/infusion/blob/master/src/framework/core/js/FluidView.js#L145 + // in order to reset the dom binder when panels are in an iframe. + // It can be be eliminated once we have the new renderer. + var userJQuery = that.container.constructor; + var context = that.container[0].ownerDocument; + var selector = that.container.selector; + that.container = userJQuery(selector, context); + // 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 or fluid.container. + // However, in this case we are manually recreating the container to ensure that it is referencing an element currently added + // to the correct Document ( e.g. iframe ) (also see: FLUID-4536). This manual recreation of the container requires us to + // manually add back the selector and context from the original container. This code and fix parallels that in + // FluidView.js fluid.container line 129 + that.container.selector = selector; + that.container.context = context; + if (that.container.length === 0) { + fluid.fail("resetDomBinder got no elements in DOM for container searching for selector " + that.container.selector); + } + fluid.initDomBinder(that, that.options.selectors); + that.events.onDomBind.fire(that); + }; + + fluid.prefs.subPanel.safePrefKey = function (prefKey) { + return prefKey.replace(/[.]/g, "_"); + }; + + /* + * Generates the model relay rules for a subpanel. + * Takes advantage of the fact that compositePanel + * uses the preference key (with "." replaced by "_"), + * as its model path. + */ + fluid.prefs.subPanel.generateRules = function (preferenceMap) { + var rules = {}; + fluid.each(preferenceMap, function (prefObj, prefKey) { + fluid.each(prefObj, function (value, prefRule) { + if (prefRule.indexOf("model.") === 0) { + rules[fluid.prefs.subPanel.safePrefKey(prefKey)] = prefRule.slice("model.".length); + } + }); + }); + return rules; + }; + + /********************************** + * Base grade for composite panel * + **********************************/ + + fluid.registerNamespace("fluid.prefs.compositePanel"); + + fluid.prefs.compositePanel.arrayMergePolicy = function (target, source) { + target = fluid.makeArray(target); + source = fluid.makeArray(source); + fluid.each(source, function (selector) { + if (target.indexOf(selector) < 0) { + target.push(selector); + } + }); + return target; + }; + + fluid.defaults("fluid.prefs.compositePanel", { + gradeNames: ["fluid.prefs.panel", "{that}.getDistributeOptionsGrade", "{that}.getSubPanelLifecycleBindings"], + mergePolicy: { + subPanelOverrides: "noexpand", + selectorsToIgnore: fluid.prefs.compositePanel.arrayMergePolicy + }, + selectors: {}, // requires selectors into the template which will act as the containers for the subpanels + selectorsToIgnore: [], // should match the selectors that are used to identify the containers for the subpanels + repeatingSelectors: [], + events: { + initSubPanels: null + }, + listeners: { + "onCreate.combineResources": "{that}.combineResources", + "onCreate.appendTemplate": { + "this": "{that}.container", + "method": "append", + "args": ["{that}.options.resources.template.resourceText"] + }, + "onCreate.initSubPanels": "{that}.events.initSubPanels", + "onCreate.hideInactive": "{that}.hideInactive", + "onCreate.surfaceSubpanelRendererSelectors": "{that}.surfaceSubpanelRendererSelectors", + "afterRender.hideInactive": "{that}.hideInactive" + }, + invokers: { + getDistributeOptionsGrade: { + funcName: "fluid.prefs.compositePanel.assembleDistributeOptions", + args: ["{that}.options.components"] + }, + getSubPanelLifecycleBindings: { + funcName: "fluid.prefs.compositePanel.subPanelLifecycleBindings", + args: ["{that}.options.components"] + }, + combineResources: { + funcName: "fluid.prefs.compositePanel.combineTemplates", + args: ["{that}.options.resources", "{that}.options.selectors"] + }, + surfaceSubpanelRendererSelectors: { + funcName: "fluid.prefs.compositePanel.surfaceSubpanelRendererSelectors", + args: ["{that}", "{that}.options.components", "{that}.options.selectors"] + }, + produceSubPanelTrees: { + funcName: "fluid.prefs.compositePanel.produceSubPanelTrees", + args: ["{that}"] + }, + expandProtoTree: { + funcName: "fluid.prefs.compositePanel.expandProtoTree", + args: ["{that}"] + }, + produceTree: { + funcName: "fluid.prefs.compositePanel.produceTree", + args: ["{that}"] + }, + hideInactive: { + funcName: "fluid.prefs.compositePanel.hideInactive", + args: ["{that}"] + }, + handleRenderOnPreference: { + funcName: "fluid.prefs.compositePanel.handleRenderOnPreference", + args: ["{that}", "{that}.refreshView", "{that}.conditionalCreateEvent", "{arguments}.0", "{arguments}.1", "{arguments}.2"] + }, + conditionalCreateEvent: { + funcName: "fluid.prefs.compositePanel.conditionalCreateEvent" + } + }, + subPanelOverrides: { + gradeNames: ["fluid.prefs.subPanel"] + }, + rendererFnOptions: { + noexpand: true, + cutpointGenerator: "fluid.prefs.compositePanel.cutpointGenerator", + subPanelRepeatingSelectors: { + expander: { + funcName: "fluid.prefs.compositePanel.surfaceRepeatingSelectors", + args: ["{that}.options.components"] + } + } + }, + components: {}, + resources: {} // template is reserved for the compositePanel's template, the subpanel template should have same key as the selector for its container. + }); + + /* + * Attempts to prefetch a components options before it is instantiated. + * Only use in cases where the instatiated component cannot be used. + */ + fluid.prefs.compositePanel.prefetchComponentOptions = function (type, options) { + var baseOptions = fluid.getMergedDefaults(type, fluid.get(options, "gradeNames")); + // TODO: awkwardly, fluid.merge is destructive on each argument! + return fluid.merge(baseOptions.mergePolicy, fluid.copy(baseOptions), options); + }; + /* + * Should only be used when fluid.prefs.compositePanel.isActivatePanel cannot. + * While this implementation doesn't require an instantiated component, it may in + * the process miss some configuration provided by distribute options and demands. + */ + fluid.prefs.compositePanel.isPanel = function (type, options) { + var opts = fluid.prefs.compositePanel.prefetchComponentOptions(type, options); + return fluid.hasGrade(opts, "fluid.prefs.panel"); + }; + + fluid.prefs.compositePanel.isActivePanel = function (comp) { + return comp && fluid.hasGrade(comp.options, "fluid.prefs.panel"); + }; + + /* + * Creates a grade containing the distributeOptions rules needed for the subcomponents + */ + fluid.prefs.compositePanel.assembleDistributeOptions = function (components) { + var gradeName = "fluid.prefs.compositePanel.distributeOptions_" + fluid.allocateGuid(); + var distributeOptions = []; + var relayOption = {}; + fluid.each(components, function (componentOptions, componentName) { + if (fluid.prefs.compositePanel.isPanel(componentOptions.type, componentOptions.options)) { + distributeOptions.push({ + source: "{that}.options.subPanelOverrides", + target: "{that > " + componentName + "}.options" + }); + } + + // Construct the model relay btw the composite panel and its subpanels + var componentRelayRules = {}; + var definedOptions = fluid.prefs.compositePanel.prefetchComponentOptions(componentOptions.type, componentOptions.options); + var preferenceMap = fluid.get(definedOptions, ["preferenceMap"]); + fluid.each(preferenceMap, function (prefObj, prefKey) { + fluid.each(prefObj, function (value, prefRule) { + if (prefRule.indexOf("model.") === 0) { + fluid.set(componentRelayRules, prefRule.slice("model.".length), "{compositePanel}.model." + fluid.prefs.subPanel.safePrefKey(prefKey)); + } + }); + }); + relayOption[componentName] = componentRelayRules; + distributeOptions.push({ + source: "{that}.options.relayOption." + componentName, + target: "{that > " + componentName + "}.options.model" + }); + }); + fluid.defaults(gradeName, { + gradeNames: ["fluid.component"], + relayOption: relayOption, + distributeOptions: distributeOptions + }); + return gradeName; + }; + + fluid.prefs.compositePanel.conditionalCreateEvent = function (value, createEvent) { + if (value) { + createEvent(); + } + }; + + + fluid.prefs.compositePanel.handleRenderOnPreference = function (that, refreshViewFunc, conditionalCreateEventFunc, value, createEvent, componentNames) { + componentNames = fluid.makeArray(componentNames); + conditionalCreateEventFunc(value, createEvent); + fluid.each(componentNames, function (componentName) { + var comp = that[componentName]; + if (!value && comp) { + comp.destroy(); + } + }); + refreshViewFunc(); + }; + + fluid.prefs.compositePanel.creationEventName = function (pref) { + return "initOn_" + pref; + }; + + fluid.prefs.compositePanel.generateModelListeners = function (conditionals) { + return fluid.transform(conditionals, function (componentNames, pref) { + var eventName = fluid.prefs.compositePanel.creationEventName(pref); + return { + func: "{that}.handleRenderOnPreference", + args: ["{change}.value", "{that}.events." + eventName + ".fire", componentNames] + }; + }); + }; + + /* + * Creates a grade containing all of the lifecycle binding configuration needed for the subpanels. + * This includes the following: + * - adding events used to trigger the initialization of the subpanels + * - adding the createOnEvent configuration for the subpanels + * - binding handlers to model changed events + * - binding handlers to afterRender and onCreate + */ + fluid.prefs.compositePanel.subPanelLifecycleBindings = function (components) { + var gradeName = "fluid.prefs.compositePanel.subPanelCreationTimingDistibution_" + fluid.allocateGuid(); + var distributeOptions = []; + var subPanelCreationOpts = { + "default": "initSubPanels" + }; + var conditionals = {}; + var listeners = {}; + var events = {}; + fluid.each(components, function (componentOptions, componentName) { + if (fluid.prefs.compositePanel.isPanel(componentOptions.type, componentOptions.options)) { + var creationEventOpt = "default"; + // would have had renderOnPreference directly sourced from the componentOptions + // however, the set of configuration specified there is restricted. + var renderOnPreference = fluid.get(componentOptions, "options.renderOnPreference"); + if (renderOnPreference) { + var pref = fluid.prefs.subPanel.safePrefKey(renderOnPreference); + var onCreateListener = "onCreate." + pref; + creationEventOpt = fluid.prefs.compositePanel.creationEventName(pref); + subPanelCreationOpts[creationEventOpt] = creationEventOpt; + events[creationEventOpt] = null; + conditionals[pref] = conditionals[pref] || []; + conditionals[pref].push(componentName); + listeners[onCreateListener] = { + listener: "{that}.conditionalCreateEvent", + args: ["{that}.model." + pref, "{that}.events." + creationEventOpt + ".fire"] + }; + } + distributeOptions.push({ + source: "{that}.options.subPanelCreationOpts." + creationEventOpt, + target: "{that}.options.components." + componentName + ".createOnEvent" + }); + } + }); + + fluid.defaults(gradeName, { + gradeNames: ["fluid.component"], + events: events, + listeners: listeners, + modelListeners: fluid.prefs.compositePanel.generateModelListeners(conditionals), + subPanelCreationOpts: subPanelCreationOpts, + distributeOptions: distributeOptions + }); + return gradeName; + }; + + /* + * Used to hide the containers of inactive sub panels. + * This is necessary as the composite panel's template is the one that has their containers and + * it would be undesirable to have them visible when their associated panel has not been created. + * Also, hiding them allows for the subpanel to initialize, as it requires their container to be present. + * The subpanels need to be initialized before rendering, for the produce function to source the rendering + * information from it. + */ + fluid.prefs.compositePanel.hideInactive = function (that) { + fluid.each(that.options.components, function (componentOpts, componentName) { + if (fluid.prefs.compositePanel.isPanel(componentOpts.type, componentOpts.options) && !fluid.prefs.compositePanel.isActivePanel(that[componentName])) { + that.locate(componentName).hide(); + } + }); + }; + + /* + * Use the renderer directly to combine the templates into a single + * template to be used by the components actual rendering. + */ + fluid.prefs.compositePanel.combineTemplates = function (resources, selectors) { + var cutpoints = []; + var tree = {children: []}; + + fluid.each(resources, function (resource, resourceName) { + if (resourceName !== "template") { + tree.children.push({ + ID: resourceName, + markup: resource.resourceText + }); + cutpoints.push({ + id: resourceName, + selector: selectors[resourceName] + }); + } + }); + + var resourceSpec = { + base: { + resourceText: resources.template.resourceText, + href: ".", + resourceKey: ".", + cutpoints: cutpoints + } + }; + + var templates = fluid.parseTemplates(resourceSpec, ["base"]); + var renderer = fluid.renderer(templates, tree, {cutpoints: cutpoints, debugMode: true}); + resources.template.resourceText = renderer.renderTemplates(); + }; + + fluid.prefs.compositePanel.rebaseSelectorName = function (memberName, selectorName) { + return memberName + "_" + selectorName; + }; + + /* + * Surfaces the rendering selectors from the subpanels to the compositePanel, + * and scopes them to the subpanel's container. + * Since this is used by the cutpoint generator, which only gets run once, we need to + * surface all possible subpanel selectors, and not just the active ones. + */ + fluid.prefs.compositePanel.surfaceSubpanelRendererSelectors = function (that, components, selectors) { + fluid.each(components, function (compOpts, compName) { + if (fluid.prefs.compositePanel.isPanel(compOpts.type, compOpts.options)) { + var opts = fluid.prefs.compositePanel.prefetchComponentOptions(compOpts.type, compOpts.options); + fluid.each(opts.selectors, function (selector, selName) { + if (!opts.selectorsToIgnore || opts.selectorsToIgnore.indexOf(selName) < 0) { + fluid.set(selectors, fluid.prefs.compositePanel.rebaseSelectorName(compName, selName), selectors[compName] + " " + selector); + } + }); + } + }); + }; + + fluid.prefs.compositePanel.surfaceRepeatingSelectors = function (components) { + var repeatingSelectors = []; + fluid.each(components, function (compOpts, compName) { + if (fluid.prefs.compositePanel.isPanel(compOpts.type, compOpts.options)) { + var opts = fluid.prefs.compositePanel.prefetchComponentOptions(compOpts.type, compOpts.options); + var rebasedRepeatingSelectors = fluid.transform(opts.repeatingSelectors, function (selector) { + return fluid.prefs.compositePanel.rebaseSelectorName(compName, selector); + }); + repeatingSelectors = repeatingSelectors.concat(rebasedRepeatingSelectors); + } + }); + return repeatingSelectors; + }; + + fluid.prefs.compositePanel.cutpointGenerator = function (selectors, options) { + var opts = { + selectorsToIgnore: options.selectorsToIgnore, + repeatingSelectors: options.repeatingSelectors.concat(options.subPanelRepeatingSelectors) + }; + return fluid.renderer.selectorsToCutpoints(selectors, opts); + }; + + fluid.prefs.compositePanel.rebaseID = function (value, memberName) { + return memberName + "_" + value; + }; + + fluid.prefs.compositePanel.rebaseParentRelativeID = function (val, memberName) { + var slicePos = "..::".length; // ..:: refers to the parentRelativeID prefix used in the renderer + return val.slice(0, slicePos) + fluid.prefs.compositePanel.rebaseID(val.slice(slicePos), memberName); + }; + + fluid.prefs.compositePanel.rebaseValueBinding = function (value, modelRelayRules) { + return fluid.find(modelRelayRules, function (oldModelPath, newModelPath) { + if (value === oldModelPath) { + return newModelPath; + } else if (value.indexOf(oldModelPath) === 0) { + return value.replace(oldModelPath, newModelPath); + } + }) || value; + }; + + fluid.prefs.compositePanel.rebaseTreeComp = function (msgResolver, model, treeComp, memberName, modelRelayRules) { + var rebased = fluid.copy(treeComp); + + if (rebased.ID) { + rebased.ID = fluid.prefs.compositePanel.rebaseID(rebased.ID, memberName); + } + + if (rebased.children) { + rebased.children = fluid.prefs.compositePanel.rebaseTree(msgResolver, model, rebased.children, memberName, modelRelayRules); + } else if (rebased.selection) { + rebased.selection = fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, rebased.selection, memberName, modelRelayRules); + } else if (rebased.messagekey) { + // converts the "UIMessage" renderer component into a "UIBound" + // and passes in the resolved message as the value. + rebased.componentType = "UIBound"; + rebased.value = msgResolver.resolve(rebased.messagekey.value, rebased.messagekey.args); + delete rebased.messagekey; + } else if (rebased.parentRelativeID) { + rebased.parentRelativeID = fluid.prefs.compositePanel.rebaseParentRelativeID(rebased.parentRelativeID, memberName); + } else if (rebased.valuebinding) { + rebased.valuebinding = fluid.prefs.compositePanel.rebaseValueBinding(rebased.valuebinding, modelRelayRules); + + if (rebased.value) { + var modelValue = fluid.get(model, rebased.valuebinding); + rebased.value = modelValue !== undefined ? modelValue : rebased.value; + } + } + + return rebased; + }; + + fluid.prefs.compositePanel.rebaseTree = function (msgResolver, model, tree, memberName, modelRelayRules) { + var rebased; + + if (fluid.isArrayable(tree)) { + rebased = fluid.transform(tree, function (treeComp) { + return fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, treeComp, memberName, modelRelayRules); + }); + } else { + rebased = fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, tree, memberName, modelRelayRules); + } + + return rebased; + }; + + fluid.prefs.compositePanel.produceTree = function (that) { + var produceTreeOption = that.options.produceTree; + var ownTree = produceTreeOption ? + (typeof (produceTreeOption) === "string" ? fluid.getGlobalValue(produceTreeOption) : produceTreeOption)(that) : + that.expandProtoTree(); + var subPanelTree = that.produceSubPanelTrees(); + var tree = { + children: ownTree.children.concat(subPanelTree.children) + }; + return tree; + }; + + fluid.prefs.compositePanel.expandProtoTree = function (that) { + var expanderOptions = fluid.renderer.modeliseOptions(that.options.expanderOptions, {ELstyle: "${}"}, that); + var expander = fluid.renderer.makeProtoExpander(expanderOptions, that); + return expander(that.options.protoTree || {}); + }; + + fluid.prefs.compositePanel.produceSubPanelTrees = function (that) { + var tree = {children: []}; + fluid.each(that.options.components, function (options, componentName) { + var subPanel = that[componentName]; + if (fluid.prefs.compositePanel.isActivePanel(subPanel)) { + var expanderOptions = fluid.renderer.modeliseOptions(subPanel.options.expanderOptions, {ELstyle: "${}"}, subPanel); + var expander = fluid.renderer.makeProtoExpander(expanderOptions, subPanel); + var subTree = subPanel.produceTree(); + subTree = fluid.get(subPanel.options, "rendererFnOptions.noexpand") ? subTree : expander(subTree); + var rebasedTree = fluid.prefs.compositePanel.rebaseTree(subPanel.msgResolver, that.model, subTree, componentName, subPanel.options.rules); + tree.children = tree.children.concat(rebasedTree.children); + } + }); + return tree; + }; + + /******************************************************************************** + * The grade that contains the connections between a panel and the prefs editor * + ********************************************************************************/ + + fluid.defaults("fluid.prefs.prefsEditorConnections", { + gradeNames: ["fluid.component"], + listeners: { + "{fluid.prefs.prefsEditor}.events.onPrefsEditorRefresh": "{fluid.prefs.panel}.refreshView" + }, + strings: {}, + parentBundle: "{fluid.prefs.prefsEditorLoader}.msgResolver" + }); + + /******************************** + * Preferences Editor Text Size * + ********************************/ + + /** + * A sub-component of fluid.prefs that renders the "text size" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.textSize", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.textSize": { + "model.textSize": "default", + "range.min": "minimum", + "range.max": "maximum" + } + }, + // The default model values represent both the expected format as well as the setting to be applied in the absence of values passed down to the component. + // i.e. from the settings store, or specific defaults derived from schema. + // Note: Except for being passed down to its subcomponent, these default values are not contributed and shared out + range: { + min: 1, + max: 2 + }, + selectors: { + textSize: ".flc-prefsEditor-min-text-size", + label: ".flc-prefsEditor-min-text-size-label", + multiplier: ".flc-prefsEditor-multiplier", + textSizeDescr: ".flc-prefsEditor-text-size-descr" + }, + selectorsToIgnore: ["textSize"], + components: { + textfieldSlider: { + type: "fluid.textfieldSlider", + container: "{that}.dom.textSize", + createOnEvent: "afterRender", + options: { + model: { + value: "{fluid.prefs.panel.textSize}.model.textSize" + }, + range: "{fluid.prefs.panel.textSize}.options.range", + sliderOptions: "{fluid.prefs.panel.textSize}.options.sliderOptions", + ariaOptions: { + "aria-labelledby": "{textSize}.options.panelOptions.labelId" + } + } + } + }, + protoTree: { + label: { + messagekey: "textSizeLabel", + decorators: { + attrs: {id: "{that}.options.panelOptions.labelId"} + } + }, + multiplier: {messagekey: "multiplier"}, + textSizeDescr: {messagekey: "textSizeDescr"} + }, + sliderOptions: { + orientation: "horizontal", + step: 0.1, + range: "min" + }, + panelOptions: { + labelId: "textSize-label-" + fluid.allocateGuid() + } + }); + + /******************************** + * Preferences Editor Text Font * + ********************************/ + + /** + * A sub-component of fluid.prefs that renders the "text font" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.textFont", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.textFont": { + "model.value": "default", + "controlValues.textFont": "enum" + } + }, + selectors: { + textFont: ".flc-prefsEditor-text-font", + label: ".flc-prefsEditor-text-font-label", + textFontDescr: ".flc-prefsEditor-text-font-descr" + }, + stringArrayIndex: { + textFont: ["textFont-default", "textFont-times", "textFont-comic", "textFont-arial", "textFont-verdana"] + }, + protoTree: { + label: {messagekey: "textFontLabel"}, + textFontDescr: {messagekey: "textFontDescr"}, + textFont: { + optionnames: "${{that}.msgLookup.textFont}", + optionlist: "${{that}.options.controlValues.textFont}", + selection: "${value}", + decorators: { + type: "fluid", + func: "fluid.prefs.selectDecorator", + options: { + styles: "{that}.options.classnameMap.textFont" + } + } + } + }, + classnameMap: null, // must be supplied by implementors + controlValues: { + textFont: ["default", "times", "comic", "arial", "verdana"] + } + }); + + /********************************* + * Preferences Editor Line Space * + *********************************/ + + /** + * A sub-component of fluid.prefs that renders the "line space" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.lineSpace", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.lineSpace": { + "model.lineSpace": "default", + "range.min": "minimum", + "range.max": "maximum" + } + }, + // The default model values represent both the expected format as well as the setting to be applied in the absence of values passed down to the component. + // i.e. from the settings store, or specific defaults derived from schema. + // Note: Except for being passed down to its subcomponent, these default values are not contributed and shared out + range: { + min: 1, + max: 2 + }, + selectors: { + lineSpace: ".flc-prefsEditor-line-space", + label: ".flc-prefsEditor-line-space-label", + multiplier: ".flc-prefsEditor-multiplier", + lineSpaceDescr: ".flc-prefsEditor-line-space-descr" + }, + selectorsToIgnore: ["lineSpace"], + components: { + textfieldSlider: { + type: "fluid.textfieldSlider", + container: "{that}.dom.lineSpace", + createOnEvent: "afterRender", + options: { + model: { + value: "{fluid.prefs.panel.lineSpace}.model.lineSpace" + }, + range: "{fluid.prefs.panel.lineSpace}.options.range", + sliderOptions: "{fluid.prefs.panel.lineSpace}.options.sliderOptions", + ariaOptions: { + "aria-labelledby": "{lineSpace}.options.panelOptions.labelId" + } + } + } + }, + protoTree: { + label: { + messagekey: "lineSpaceLabel", + decorators: { + attrs: {id: "{that}.options.panelOptions.labelId"} + } + }, + multiplier: {messagekey: "multiplier"}, + lineSpaceDescr: {messagekey: "lineSpaceDescr"} + }, + sliderOptions: { + orientation: "horizontal", + step: 0.1, + range: "min" + }, + panelOptions: { + labelId: "lineSpace-label-" + fluid.allocateGuid() + } + }); + + /******************************* + * Preferences Editor Contrast * + *******************************/ + + /** + * A sub-component of fluid.prefs that renders the "contrast" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.contrast", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.contrast": { + "model.value": "default", + "controlValues.theme": "enum" + } + }, + listeners: { + "afterRender.style": "{that}.style" + }, + selectors: { + themeRow: ".flc-prefsEditor-themeRow", + themeLabel: ".flc-prefsEditor-theme-label", + themeInput: ".flc-prefsEditor-themeInput", + label: ".flc-prefsEditor-contrast-label", + contrastDescr: ".flc-prefsEditor-contrast-descr" + }, + styles: { + defaultThemeLabel: "fl-prefsEditor-contrast-defaultThemeLabel" + }, + stringArrayIndex: { + theme: ["contrast-default", "contrast-bw", "contrast-wb", "contrast-by", "contrast-yb", "contrast-lgdg"] + }, + repeatingSelectors: ["themeRow"], + protoTree: { + label: {messagekey: "contrastLabel"}, + contrastDescr: {messagekey: "contrastDescr"}, + expander: { + type: "fluid.renderer.selection.inputs", + rowID: "themeRow", + labelID: "themeLabel", + inputID: "themeInput", + selectID: "theme-radio", + tree: { + optionnames: "${{that}.msgLookup.theme}", + optionlist: "${{that}.options.controlValues.theme}", + selection: "${value}" + } + } + }, + controlValues: { + theme: ["default", "bw", "wb", "by", "yb", "lgdg"] + }, + markup: { + // Aria-hidden needed on fl-preview-A and Display 'a' created as pseudo-content in css to prevent AT from reading out display 'a' on IE, Chrome, and Safari + // Aria-hidden needed on fl-crossout to prevent AT from trying to read crossout symbol in Safari + label: "%theme
" + }, + invokers: { + style: { + funcName: "fluid.prefs.panel.contrast.style", + args: [ + "{that}.dom.themeLabel", + "{that}.msgLookup.theme", + "{that}.options.markup.label", + "{that}.options.controlValues.theme", + "default", + "{that}.options.classnameMap.theme", + "{that}.options.styles.defaultThemeLabel" + ] + } + } + }); + + fluid.prefs.panel.contrast.style = function (labels, strings, markup, theme, defaultThemeName, style, defaultLabelStyle) { + fluid.each(labels, function (label, index) { + label = $(label); + + var themeValue = strings[index]; + label.html(fluid.stringTemplate(markup, { + theme: themeValue + })); + + // Aria-label set to prevent Firefox from reading out the display 'a' + label.attr("aria-label", themeValue); + + var labelTheme = theme[index]; + if (labelTheme === defaultThemeName) { + label.addClass(defaultLabelStyle); + } + label.addClass(style[labelTheme]); + }); + }; + + /************************************** + * Preferences Editor Layout Controls * + **************************************/ + + /** + * A sub-component of fluid.prefs that renders the "layout and navigation" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.layoutControls", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.tableOfContents": { + "model.toc": "default" + } + }, + selectors: { + toc: ".flc-prefsEditor-toc", + label: ".flc-prefsEditor-toc-label", + tocDescr: ".flc-prefsEditor-toc-descr" + }, + protoTree: { + label: {messagekey: "tocLabel"}, + tocDescr: {messagekey: "tocDescr"}, + toc: "${toc}" + } + }); + + /************************************** + * Preferences Editor Emphasize Links * + **************************************/ + /** + * A sub-component of fluid.prefs that renders the "links and buttons" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.emphasizeLinks", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.emphasizeLinks": { + "model.links": "default" + } + }, + selectors: { + links: ".flc-prefsEditor-links", + linksChoiceLabel: ".flc-prefsEditor-links-choice-label" + }, + protoTree: { + linksChoiceLabel: {messagekey: "linksChoiceLabel"}, + links: "${links}" + } + }); + + /************************************ + * Preferences Editor Inputs Larger * + ************************************/ + /** + * A sub-component of fluid.prefs that renders the "links and buttons" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.inputsLarger", { + gradeNames: ["fluid.prefs.panel"], + preferenceMap: { + "fluid.prefs.inputsLarger": { + "model.inputsLarger": "default" + } + }, + selectors: { + inputsLarger: ".flc-prefsEditor-inputs-larger", + inputsChoiceLabel: ".flc-prefsEditor-links-inputs-choice-label" + }, + protoTree: { + inputsChoiceLabel: {messagekey: "inputsChoiceLabel"}, + inputsLarger: "${inputsLarger}" + } + }); + + /************************************* + * Preferences Editor Links Controls * + *************************************/ + /** + * A sub-component of fluid.prefs that renders the "links and buttons" panel of the user preferences interface. + */ + fluid.defaults("fluid.prefs.panel.linksControls", { + gradeNames: ["fluid.prefs.compositePanel"], + selectors: { + label: ".flc-prefsEditor-linksControls-label" + }, + protoTree: { + label: {messagekey: "linksControlsLabel"} + } + }); + + /******************************************************** + * Preferences Editor Select Dropdown Options Decorator * + ********************************************************/ + + /** + * A sub-component that decorates the options on the select dropdown list box with the css style + */ + fluid.defaults("fluid.prefs.selectDecorator", { + gradeNames: ["fluid.viewComponent"], + listeners: { + "onCreate.decorateOptions": "fluid.prefs.selectDecorator.decorateOptions" + }, + styles: { + preview: "fl-preview-theme" + } + }); + + fluid.prefs.selectDecorator.decorateOptions = function (that) { + fluid.each($("option", that.container), function (option) { + var styles = that.options.styles; + $(option).addClass(styles.preview + " " + styles[fluid.value(option)]); + }); + }; + +})(jQuery, fluid_2_0_0); diff --git a/lib/infusion/src/framework/preferences/js/PrefsEditor.js b/lib/infusion/src/framework/preferences/js/PrefsEditor.js new file mode 100644 index 0000000..3cd9958 --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/PrimaryBuilder.js b/lib/infusion/src/framework/preferences/js/PrimaryBuilder.js new file mode 100644 index 0000000..99c86ce --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/SelfVoicingEnactor.js b/lib/infusion/src/framework/preferences/js/SelfVoicingEnactor.js new file mode 100644 index 0000000..fbcc42c --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/SelfVoicingPanel.js b/lib/infusion/src/framework/preferences/js/SelfVoicingPanel.js new file mode 100644 index 0000000..61fc16e --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/SelfVoicingSchemas.js b/lib/infusion/src/framework/preferences/js/SelfVoicingSchemas.js new file mode 100644 index 0000000..12a13ac --- /dev/null +++ b/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); diff --git a/lib/infusion/src/framework/preferences/js/SeparatedPanelPrefsEditor.js b/lib/infusion/src/framework/preferences/js/SeparatedPanelPrefsEditor.js new file mode 100644 index 0000000..c985054 --- /dev/null +++ b/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 = $("