-
Couldn't load subscription status.
- Fork 6
GPII-2933 Common Event Bindings #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
669e19d
e44d0f2
c6f021b
b5a1070
6e7d524
b5c2d4d
2dd12a6
3bd28a7
0be5062
d7ed31a
cb18acc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| /* global fluid */ | ||
| (function () { | ||
| "use strict"; | ||
|
|
||
| /** | ||
| * GPII Binder Markup Events Grade | ||
| * This grade allows binding typical HTML events such as | ||
| * mouse clicks and keypress events to selectors each time | ||
| * the markup is rendered for a component. (Meaning they will | ||
| * also apply if a component is rerendered after a model refresh | ||
| * or similar situation) | ||
| * | ||
| * It adds a new area to a grades options called `markupEventBindings` | ||
| * which allows binding `selectors` to jQuery events. Theoretically | ||
| * other event constructs could be supported in the future, but only | ||
| * jQuery events are implemented at the time of writing. | ||
| * | ||
| * Binding is initiated when the components `onMarkupRendered` event is | ||
| * fired. If you are using a grade derived from `gpii.handlebars.templateAware` | ||
| * this event will be automatically fired when the handlebars markup is | ||
| * rendered. | ||
| * | ||
| * Example usage of adding a click handler to a selector productListLinks. | ||
| * ``` | ||
| * markupEventBindings: { | ||
| * productListLinks: { | ||
| * // type: jQuery <- Defaults to jQuery but could be configured ITF | ||
| * method: "click", | ||
| * args: ["{that}.selectProduct"] | ||
| * } | ||
| * } | ||
| * ``` | ||
| */ | ||
| fluid.defaults("gpii.binder.bindMarkupEvents", { | ||
| mergePolicy: { | ||
| decorators: "noexpand" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this "decorators"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In other words, is there a pattern we're emulating? Just curious. |
||
| }, | ||
| events: { | ||
| onDomBind: null, | ||
| onDomUnbind: null, | ||
| onMarkupRendered: null | ||
| }, | ||
| listeners: { | ||
| onMarkupRendered: "{that}.events.onDomBind.fire({that}, {that}.container)", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These should all be namespaced. I'd also suggest using the "long form" to this shorter notation. That way someone can at least opt out of part of the contract in a derived grade by changing the "funcName" to |
||
| onDestroy: "{that}.events.onDomUnbind.fire({that}, {that}.container)", | ||
| onDomBind: "fluid.decoratorViewComponent.processDecorators({that}, {that}.options.markupEventBindings)" | ||
| } | ||
| }); | ||
|
|
||
| /** | ||
| * | ||
| * Markup Event Binding | ||
| * | ||
| * @typedef {Object} MarkupEventBinding | ||
| * @property {String} type - Currently the only supported value is `jQuery`. This property | ||
| * can also be omitted, in which case it will default to `jQuery`. | ||
| * @property {String|Array} method - The DOM Event we are binding to such as `click`. If we | ||
| * want to listen for multiple events in the same binding this can be an array of event | ||
| * types such as `["click", "keypress"]`. | ||
| * @property {Array} args - A list of arguments to be passed to the event. This supports the | ||
| * usual range of Fluid IoC syntax. Typically, this will be an invoker on the component to be | ||
| * called when the event is triggered. | ||
| */ | ||
|
|
||
| /** | ||
| * | ||
| * Markup Event Bindings | ||
| * | ||
| * This is an object containing mappings of selector names to `MarkupEventBinding` | ||
| * declarations. Each key should be a name corresponding to a selector in the | ||
| * components `selectors` option block. | ||
| * | ||
| * @typedef {Object} MarkupEventBindings | ||
| */ | ||
|
|
||
| fluid.registerNamespace("fluid.decoratorViewComponent"); | ||
|
|
||
| // | ||
| // The methods below might be generic enough to go straight to infusion | ||
| // | ||
|
|
||
| /** | ||
| * | ||
| * Expands string encoded arguments to the event invoker, filling | ||
| * in any compact string versions of infusion invokers. | ||
| * | ||
| * @param {gpii.binder.bindMarkupEvents} that - Any infusion component with sub-grade `bindMarkupEvents`. | ||
| * @param {String|Object} arg - A single argument being passed to the event invoker. | ||
| * @param {String} name - The name for the invoker. | ||
| * @return {Object} The expanded argument. | ||
| */ | ||
| fluid.expandCompoundArg = function (that, arg, name) { | ||
sgithens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var expanded = arg; | ||
| if (typeof(arg) === "string") { | ||
| if (arg.indexOf("(") !== -1) { | ||
| var invokerec = fluid.compactStringToRec(arg, "invoker"); | ||
| // TODO: perhaps a a courtesy we could expose {node} or even {this} | ||
| expanded = fluid.makeInvoker(that, invokerec, name); | ||
| } else { | ||
| expanded = fluid.expandImmediate(arg, that); | ||
| } | ||
| } | ||
| return expanded; | ||
| }; | ||
|
|
||
| /** | ||
| * | ||
| * Processes a single markup event binding decorator of event type jQuery. | ||
| * Currently the only type of event supported is jQuery events, so this does | ||
| * all the work. | ||
| * | ||
| * @param {MarkupEventBinding} dec - The single binding decorator being processed. | ||
| * @param {DOMNode} node - The node we are listening to for the specified events. | ||
| * @param {gpii.binder.bindMarkupEvents} that - Any infusion component with sub-grade `bindMarkupEvents`. | ||
| * @param {String} name - Name that will be used for the invoker created to handle | ||
| * this event. | ||
| * @return {jQuery[]} Array of jQuery objects which the events attached to them. | ||
| */ | ||
| fluid.processjQueryDecorator = function (dec, node, that, name) { | ||
sgithens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var args = fluid.makeArray(dec.args); | ||
| var expanded = fluid.transform(args, function (arg, index) { | ||
| return fluid.expandCompoundArg(that, arg, name + " argument " + index); | ||
| }); | ||
| fluid.log("Got expanded value of ", expanded, " for jQuery decorator"); | ||
| // Support for listing multiple methods in an array, or just a single string method | ||
| var methods = [dec.method]; | ||
| var togo = []; | ||
| if (fluid.isArrayable(dec.method)) { | ||
| methods = dec.method; | ||
| } | ||
| fluid.each(methods, function (method) { | ||
| var func = node[method]; | ||
| togo.push(func.apply(node, expanded)); | ||
| }); | ||
| return togo; | ||
| }; | ||
|
|
||
| /** | ||
| * | ||
| * Function to process the markup binding decorators and create the events described | ||
| * by them. The markup needs to be rendered and settled before this can be called. | ||
| * | ||
| * @param {gpii.binder.bindMarkupEvents} that - Any component inheriting from `bindMarkupEvents`. | ||
| * @param {MarkupEventBindings} decorators - Markup Event Binding decorators on the component. | ||
| */ | ||
| fluid.decoratorViewComponent.processDecorators = function (that, decorators) { | ||
| fluid.each(decorators, function (val, key) { | ||
| var node = that.locate(key); | ||
| if (node.length > 0) { | ||
| var name = "Decorator for DOM node with selector " + key + " for component " + fluid.dumpThat(that); | ||
| // val can be an array to support multiple event handlers | ||
| var handlerDecs = fluid.isArrayable(val) ? val : [val]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just use |
||
| fluid.each(handlerDecs, function (nextVal) { | ||
| var decs = fluid.makeArray(nextVal); | ||
| fluid.each(decs, function (dec) { | ||
| // If no type is specified default to jQuery | ||
| if (!dec.type || dec.type === "jQuery") { | ||
| fluid.processjQueryDecorator(dec, node, that, name); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| }); | ||
| }; | ||
| })(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| /* globals fluid, jqUnit */ | ||
| (function () { | ||
| "use strict"; | ||
| var gpii = fluid.registerNamespace("gpii"); | ||
|
|
||
| // Component to test support for common event bindings | ||
| fluid.defaults("gpii.tests.binder.commonEventBindings", { | ||
| gradeNames: ["gpii.tests.binder.base", "gpii.binder.bindMarkupEvents"], | ||
| bindings: { | ||
| }, | ||
| selectors: { | ||
| inputButton: "#input-button", | ||
| inputKeydown: "#input-button", | ||
| paragraphClick: ".paragraph-click", | ||
| multipleEventInputButton: "#input-multiple-events-button", | ||
| multipleEventHandlerInputButton: "#input-multiple-handlers-events-button" | ||
| }, | ||
| markupEventBindings: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also supply a test which shows that these handlers can be overridden cleanly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a test where 1) Some are left as is, 2) some are overridden, and 3) some new one is introduced. |
||
| inputButton: { | ||
sgithens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| method: "click", | ||
| args: ["{that}.handleInputClick"] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Supply further tests to validate the primitive kinds of "compact" IoC syntax we support which allows for args There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for this tip, I didn't know this was possibly actually, and will simplify my usage in the capture tool, PPT, and elsewhere |
||
| }, | ||
| inputKeydown: { | ||
| method: "keydown", | ||
| args: ["{that}.handleInputClick"] | ||
| }, | ||
| paragraphClick: { | ||
| method: "click", | ||
| args: ["gpii.tests.binder.commonEventBindings.handleParagraphClick({arguments}.0)"] | ||
| }, | ||
| multipleEventInputButton: { | ||
sgithens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| method: ["click", "keydown"], | ||
| args: ["{that}.handleInputClick"] | ||
| }, | ||
| multipleEventHandlerInputButton: [ | ||
| { | ||
| method: "click", | ||
| args: ["{that}.handleInputClick"] | ||
| }, | ||
| { | ||
| method: "keydown", | ||
| args: ["{that}.handleInputKeydown"] | ||
| } | ||
| ] | ||
| }, | ||
| invokers: { | ||
| handleInputClick: { | ||
| funcName: "gpii.tests.binder.commonEventBindings.handleInputClick", | ||
| args: ["{arguments}.0"] | ||
| }, | ||
| handleInputKeydown: { | ||
| funcName: "gpii.tests.binder.commonEventBindings.handleInputKeydown", | ||
| args: ["{arguments}.0"] | ||
| } | ||
| }, | ||
| listeners: { | ||
| "onCreate.markupEventBindings": "{that}.events.onMarkupRendered.fire()" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the kind of thing I'd also write out, what if someone needs to do other work before firing the event? I guess they can just rewrite the whole string. |
||
| } | ||
| }); | ||
|
|
||
| gpii.tests.binder.commonEventBindings.handleInputClick = function (event) { | ||
| jqUnit.assertEquals("We just clicked an input...", "INPUT", event.target.nodeName); | ||
| }; | ||
|
|
||
| gpii.tests.binder.commonEventBindings.handleInputKeydown = function (event) { | ||
| jqUnit.assertEquals("We just keydowned an input...", "INPUT", event.target.nodeName); | ||
| }; | ||
|
|
||
| gpii.tests.binder.commonEventBindings.handleParagraphClick = function (event) { | ||
| jqUnit.assertEquals("We just clicked a paragraph...", "P", event.target.nodeName); | ||
| }; | ||
|
|
||
| fluid.defaults("gpii.tests.binder.commonEventBindings.caseHolder", { | ||
| gradeNames: ["gpii.tests.binder.caseHolder"], | ||
| rawModules: [{ | ||
| name: "Testing support for common event bindings...", | ||
| tests: [ | ||
| { | ||
| name: "Test markup event binding for click on an input button", | ||
| type: "test", | ||
| expect: 1, | ||
| sequence: [ | ||
| { | ||
| func: "gpii.tests.binder.clickSelector", | ||
| args: ["#input-button"] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| name: "Test markup event binding for keydown on an input button", | ||
| type: "test", | ||
| expect: 1, | ||
| sequence: [ | ||
| { | ||
| func: "gpii.tests.binder.keydownSelector", | ||
| args: ["#input-button"] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| name: "Test markup event binding for click on a paragraph with a class", | ||
| type: "test", | ||
| expect: 1, | ||
| sequence: [ | ||
| { | ||
| func: "gpii.tests.binder.clickSelector", | ||
| args: [".paragraph-click"] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| name: "Test markup event binding for both `click` and `keydown` on an input button", | ||
| type: "test", | ||
| expect: 2, | ||
| sequence: [ | ||
| { | ||
| func: "gpii.tests.binder.clickSelector", | ||
| args: ["#input-multiple-events-button"] | ||
| }, | ||
| { | ||
| func: "gpii.tests.binder.keydownSelector", | ||
| args: ["#input-multiple-events-button"] | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| name: "Test markup event binding for both `click` and `keydown` on an input button, each using a different handler", | ||
| type: "test", | ||
| expect: 2, | ||
| sequence: [ | ||
| { | ||
| func: "gpii.tests.binder.clickSelector", | ||
| args: ["#input-multiple-handlers-events-button"] | ||
| }, | ||
| { | ||
| func: "gpii.tests.binder.keydownSelector", | ||
| args: ["#input-multiple-handlers-events-button"] | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| }] | ||
| }); | ||
|
|
||
| fluid.defaults("gpii.tests.binder.commonEventBindings.environment", { | ||
| gradeNames: ["gpii.tests.binder.environment"], | ||
| markupFixture: ".viewport-common-event-bindings", | ||
| binderGradeNames: ["gpii.tests.binder.commonEventBindings"], | ||
| moduleName: "Testing common event bindings", | ||
| components: { | ||
| commonEventBindingsTests: { | ||
| type: "gpii.tests.binder.commonEventBindings.caseHolder" | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| gpii.tests.binder.commonEventBindings.environment(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it matters less on the browser side (I think?), but for consistency with the rest of the package, this should use |
||
| })(); | ||
Uh oh!
There was an error while loading. Please reload this page.