UNPKG

marko-widgets

Version:

Module to support binding of behavior to rendered UI components rendered on the server or client

179 lines (148 loc) 6.93 kB
/* * Copyright 2011 eBay Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var bubbleEventsLookup = {}; require('../../lib/bubble').forEach(function(eventType) { bubbleEventsLookup[eventType] = true; }); function isBubbleEvent(eventType) { return bubbleEventsLookup.hasOwnProperty(eventType); } function isUpperCase(c) { return c == c.toUpperCase(); } function addBubblingEventListener(transformHelper, eventType, targetMethod) { var containingWidgetNode = transformHelper.getContainingWidgetNode(); var el = transformHelper.el; if (!containingWidgetNode) { transformHelper.addError('Unable to handle event "' + eventType + '". HTML element is not nested within a widget.'); return; } var builder = transformHelper.builder; var attrValue; var widgetIdExpression = builder.memberExpression( builder.identifier('widget'), builder.identifier('id')); if (targetMethod.type === 'Literal') { // We know the event handler is method is no conditional so we set the attribute correctly at compile time attrValue = builder.concat( targetMethod, builder.literal('|'), widgetIdExpression); } else { // The event handler method is conditional and it may resolve to a null method name. Therefore, // we need to use a runtime helper to set the value correctly. var markoWidgetsEventFuncId = transformHelper.context.importModule('markoWidgets_event', transformHelper.getMarkoWidgetsRequirePath('marko-widgets/taglib/helpers/event')); attrValue = builder.functionCall(markoWidgetsEventFuncId, [ targetMethod, widgetIdExpression ]); } el.setAttributeValue('data-w-on' + eventType.value, attrValue); } function addDirectEventListener(transformHelper, eventType, targetMethod) { var builder = transformHelper.builder; var el = transformHelper.el; var addDomEvent = builder.memberExpression( builder.identifier('widget'), builder.identifier('addDomEvent')); transformHelper.assignWidgetId(true /* repeated */); var addDomEventFunctionCall = builder.functionCall( addDomEvent, [ eventType, targetMethod, transformHelper.getNestedIdExpression() ]); el.onBeforeGenerateCode((event) => { event.insertCode(addDomEventFunctionCall); }); // Also add another DOM element that will be used to var containingWidgetNode = transformHelper.getContainingWidgetNode(); containingWidgetNode.setAttributeValue('hasDomEvents', builder.literal(1)); } function addCustomEventListener(transformHelper, eventType, targetMethod) { // Make sure the widget has an assigned scope ID so that we can bind the custom event listener var widgetArgs = transformHelper.getWidgetArgs(); widgetArgs.addCustomEvent(eventType, targetMethod); } module.exports = function handleWidgetEvents() { var el = this.el; var builder = this.builder; var isCustomTag = el.type !== 'HtmlElement'; // We configured the Marko compiler to attach a flag to nodes that // have one or more attributes that match the "w-on*" pattern. // We still need to loop over the properties to find and handle // the properties corresponding to those attributes. var hasWidgetEvents = this.el.isFlagSet('hasWidgetEvents') === true; if (hasWidgetEvents) { var attrs = el.getAttributes().concat([]); attrs.forEach((attr) => { var attrName = attr.name; if (!attrName || !attrName.startsWith('w-on')) { return; } el.removeAttribute(attrName); var eventType = attrName.substring(4); // Chop off "w-on" var targetMethod = attr.value; if (isCustomTag) { var widgetArgs = this.getWidgetArgs(); if (widgetArgs.getId() == null) { widgetArgs.setId(builder.literal(this.nextUniqueId())); } // We are adding an event listener for a custom event (not a DOM event) if (eventType.startsWith('-')) { // Remove the leading dash. // Example: w-on-before-show → before-show eventType = eventType.substring(1); } else if (isUpperCase(eventType.charAt(0))) { // Convert first character to lower case: // Example: w-onBeforeShow → beforeShow eventType = eventType.charAt(0).toLowerCase() + eventType.substring(1); } eventType = builder.literal(eventType); // Node is for a custom tag addCustomEventListener(this, eventType, targetMethod); } else { // We are adding an event listener for a DOM event (not a custom event) // if (eventType.startsWith('-')) { // Remove the leading dash. // Example: w-on-before-show → before-show eventType = eventType.substring(1); } // Normalize DOM event types to be all lower case eventType = eventType.toLowerCase(); // Node is for an HTML element so treat the event as a DOM event var willBubble = isBubbleEvent(eventType); eventType = builder.literal(eventType); if (willBubble) { // The event is white listed for bubbling so we know that // we have already attached a listener on document.body // that can be used to handle the event. We will add // a "data-w-on{eventType}" attribute to the output HTML // for this element that will be used to map the event // to a method on the containing widget. addBubblingEventListener(this, eventType, targetMethod); } else { // The event does not bubble so we must attach a DOM // event listener directly to the target element. addDirectEventListener(this, eventType, targetMethod); } } }); } };