diff --git a/src/framework/core/js/Fluid.js b/src/framework/core/js/Fluid.js index 4d423644c9..3019fefd59 100644 --- a/src/framework/core/js/Fluid.js +++ b/src/framework/core/js/Fluid.js @@ -23,6 +23,11 @@ Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt + +Includes code from Underscore.js 1.4.3 +http://underscorejs.org +(c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +Underscore may be freely distributed under the MIT license. */ /* global console */ @@ -859,6 +864,35 @@ var fluid = fluid || fluid_3_0_0; } }; + /** + * Copied from Underscore.js 1.4.3 - see licence at head of this file + * + * Will execute the passed in function after the specified about of time since it was last executed. + * @param {Function} func - the function to execute + * @param {Number} wait - the number of milliseconds to wait before executing the function + * @param {Boolean} immediate - Whether to trigger the function at the start (true) or end (false) of + * the wait interval. + */ + fluid.debounce = function (func, wait, immediate) { + var timeout, result; + return function () { + var context = this, args = arguments; + var later = function () { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }; + /** Calls Object.freeze at each level of containment of the supplied object * @return The supplied argument, recursively frozen */ diff --git a/src/framework/preferences/js/ArrowScrolling.js b/src/framework/preferences/js/ArrowScrolling.js index 1f718b0161..244f331b4e 100644 --- a/src/framework/preferences/js/ArrowScrolling.js +++ b/src/framework/preferences/js/ArrowScrolling.js @@ -27,12 +27,14 @@ var fluid_3_0_0 = fluid_3_0_0 || {}; // panels: "", // should be supplied by the fluid.prefs.prefsEditor grade. scrollContainer: ".flc-prefsEditor-scrollContainer" }, + onScrollDelay: 100, // in ms, used to set the delay for debouncing the scroll event relay model: { // panelMaxIndex: null, // determined by the number of panels calculated after the onPrefsEditorMarkupReady event fired panelIndex: 0 }, events: { - beforeReset: null // should be fired by the fluid.prefs.prefsEditor grade + beforeReset: null, // should be fired by the fluid.prefs.prefsEditor grade + onScroll: null }, modelRelay: { target: "panelIndex", @@ -49,10 +51,23 @@ var fluid_3_0_0 = fluid_3_0_0 || {}; "panelIndex": { listener: "fluid.prefs.arrowScrolling.scrollToPanel", args: ["{that}", "{change}.value"], + excludeSource: ["scrollEvent"], namespace: "scrollToPanel" } }, listeners: { + "onReady.scrollEvent": { + "this": "{that}.dom.scrollContainer", + method: "scroll", + args: [{ + expander: { + // Relaying the scroll event to onScroll but debounced to reduce the rate of fire. A high rate + // of fire may negatively effect performance for complex handlers. + func: "fluid.debounce", + args: ["{that}.events.onScroll.fire", "{that}.options.onScrollDelay"] + } + }] + }, "onReady.windowResize": { "this": window, method: "addEventListener", @@ -77,6 +92,16 @@ var fluid_3_0_0 = fluid_3_0_0 || {}; "beforeReset.resetPanelIndex": { listener: "{that}.applier.fireChangeRequest", args: {path: "panelIndex", value: 0, type: "ADD", source: "reset"} + }, + "onScroll.setPanelIndex": { + changePath: "panelIndex", + value: { + expander: { + funcName: "fluid.prefs.arrowScrolling.getClosestPanelIndex", + args: "{that}.dom.panels" + } + }, + source: "scrollEvent" } }, invokers: { @@ -95,7 +120,6 @@ var fluid_3_0_0 = fluid_3_0_0 || {}; }, target: "{that > fluid.prefs.panel}.options.listeners" }] - }); fluid.prefs.arrowScrolling.calculatePanelMaxIndex = function (panels) { @@ -118,4 +142,17 @@ var fluid_3_0_0 = fluid_3_0_0 || {}; } }; + fluid.prefs.arrowScrolling.getClosestPanelIndex = function (panels) { + var panelArray = fluid.transform(panels, function (panel, idx) { + return { + index: idx, + offset: Math.abs($(panel).offset().left) + }; + }); + panelArray.sort(function (a, b) { + return a.offset - b.offset; + }); + return fluid.get(panelArray, ["0", "index"]) || 0; + }; + })(jQuery, fluid_3_0_0); diff --git a/tests/framework-tests/core/js/FluidJSTests.js b/tests/framework-tests/core/js/FluidJSTests.js index eaf7afe338..ffc564456b 100644 --- a/tests/framework-tests/core/js/FluidJSTests.js +++ b/tests/framework-tests/core/js/FluidJSTests.js @@ -307,6 +307,27 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); }); + fluid.tests.debounceTests = [1, 2, 3, 4 ,5]; + + jqUnit.asyncTest("fluid.debounce", function () { + var result = {}; + var lead = fluid.debounce(function (val) { + result.lead = val; + }, 3, true); + var trail = fluid.debounce(function (val) { + result.trail = val; + }, 3); + + setTimeout(function () { + jqUnit.assertEquals("The first value should be returned when accepting the leading response", fluid.tests.debounceTests[0], result.lead); + jqUnit.assertEquals("The last value should be returned when accepting the trailing response", fluid.tests.debounceTests[4], result.trail); + jqUnit.start(); + }, 5); + + fluid.each(fluid.tests.debounceTests, lead); + fluid.each(fluid.tests.debounceTests, trail); + }); + jqUnit.test("merge", function () { jqUnit.expect(8); diff --git a/tests/framework-tests/preferences/js/SeparatedPanelPrefsEditorResponsiveTests.js b/tests/framework-tests/preferences/js/SeparatedPanelPrefsEditorResponsiveTests.js index 0023ff5414..b71bc523ea 100644 --- a/tests/framework-tests/preferences/js/SeparatedPanelPrefsEditorResponsiveTests.js +++ b/tests/framework-tests/preferences/js/SeparatedPanelPrefsEditorResponsiveTests.js @@ -182,6 +182,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt jqUnit.assertNotEquals(testName + ": The panel at index " + idx + " should not be scrolled into view and have an offset greater or less than 0", 0, panelOffset); } }); + + jqUnit.assertEquals("fluid.prefs.arrowScrolling.getClosestPanelIndex should return " + panelIndex, panelIndex, fluid.prefs.arrowScrolling.getClosestPanelIndex(panels)); }; fluid.tests.prefs.responsive.clickArrow = function (elm, direction) { @@ -212,7 +214,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt modules: [{ name: "Separated panel integration tests", tests: [{ - expect: 70, + expect: 77, name: "Separated panel integration tests", sequenceGrade: "fluid.tests.prefs.responsive.iframeSequence", sequence: [{ @@ -265,6 +267,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt listener: "jqUnit.assert", event: "{separatedPanel}.prefsEditor.events.onSignificantDOMChange", args: ["A window resize event triggered the onSignificantDOMChange event"] + }, { + jQueryTrigger: "scroll", + element: "{separatedPanel}.prefsEditor.dom.scrollContainer" + }, { + listener: "jqUnit.assert", + event: "{separatedPanel}.prefsEditor.events.onScroll", + args: ["A scroll event triggered the onScroll event"] }] }] }] @@ -305,7 +314,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt modules: [{ name: "Separated panel initial panelIndex tester", tests: [{ - expect: 39, + expect: 40, name: "Separated panel initial panelIndex tester", sequenceGrade: "fluid.tests.prefs.responsive.iframeSequence", sequence: [{