createLetExpressions(resources, letElement) {
    let expressions = [];
    let attributes = letElement.attributes;

    let attr;

    let parts;
    let attrName;
    let attrValue;
    let command;
    let toBindingContextAttr = this.toBindingContextAttr;
    let toBindingContext = letElement.hasAttribute(toBindingContextAttr);
    for (let i = 0, ii = attributes.length; ii > i; ++i) {
      attr = attributes[i];
      attrName = attr.name;
      attrValue = attr.nodeValue;
      parts = attrName.split('.');

      if (attrName === toBindingContextAttr) {
        continue;
      }

      if (parts.length === 2) {
        command = parts[1];
        if (command !== 'bind') {
          LogManager.getLogger('templating-binding-language').warn(`Detected invalid let command. Expected "${parts[0]}.bind", given "${attrName}"`);
          continue;
        }
        expressions.push(new LetExpression(this.observerLocator, camelCase(parts[0]), this.parser.parse(attrValue), resources.lookupFunctions, toBindingContext));
      } else {
        attrName = camelCase(attrName);
        parts = this.parseInterpolation(resources, attrValue);
        if (parts === null) {
          LogManager.getLogger('templating-binding-language').warn(`Detected string literal in let bindings. Did you mean "${attrName}.bind=${attrValue}" or "${attrName}=\${${attrValue}}" ?`);
        }
        if (parts) {
          expressions.push(new LetInterpolationBindingExpression(this.observerLocator, attrName, parts, resources.lookupFunctions, toBindingContext));
        } else {
          expressions.push(new LetExpression(this.observerLocator, attrName, new LiteralString(attrValue), resources.lookupFunctions, toBindingContext));
        }
      }
    }
    return expressions;
  }
  map(elementName, attributeName) {
    if (this.svg.isStandardSvgAttribute(elementName, attributeName)) {
      return attributeName;
    }
    elementName = elementName.toLowerCase();
    attributeName = attributeName.toLowerCase();
    const element = this.elements[elementName];
    if (element !== undefined && attributeName in element) {
      return element[attributeName];
    }
    if (attributeName in this.allElements) {
      return this.allElements[attributeName];
    }

    if (/(?:^data-)|(?:^aria-)|:/.test(attributeName)) {
      return attributeName;
    }
    return camelCase(attributeName);
  }
  AttributeMap.prototype.map = function map(elementName, attributeName) {
    if (this.svg.isStandardSvgAttribute(elementName, attributeName)) {
      return attributeName;
    }
    elementName = elementName.toLowerCase();
    attributeName = attributeName.toLowerCase();
    var element = this.elements[elementName];
    if (element !== undefined && attributeName in element) {
      return element[attributeName];
    }
    if (attributeName in this.allElements) {
      return this.allElements[attributeName];
    }

    if (/(^data-)|(^aria-)|:/.test(attributeName)) {
      return attributeName;
    }
    return camelCase(attributeName);
  };
Esempio n. 4
0
  /**
   * Checks whether the provided class contains any resource conventions
   * @param target Target class to extract metadata based on convention
   * @param existing If supplied, all custom element / attribute metadata extracted from convention will be apply to this instance
   */
  static convention(target: Function, existing?: HtmlBehaviorResource): HtmlBehaviorResource | ValueConverterResource | BindingBehaviorResource | ViewEngineHooksResource {
    let resource;
    // Use a simple string to mark that an HtmlBehaviorResource instance
    // has been applied all resource information from its target view model class
    // to prevent subsequence call re initialization all info again
    if (existing && conventionMark in existing) {
      return existing;
    }
    if ('$resource' in target) {
      let config = target.$resource;
      // 1. check if resource config is a string
      if (typeof config === 'string') {
        // it's a custom element, with name is the resource variable
        // static $resource = 'my-element'
        resource = existing || new HtmlBehaviorResource();
        resource[conventionMark] = true;
        if (!resource.elementName) {
          // if element name was not specified before
          resource.elementName = validateBehaviorName(config, 'custom element');
        }
      } else {
        // 2. if static config is not a string, normalize into an config object
        if (typeof config === 'function') {
          // static $resource() {  }
          config = config.call(target);
        }
        if (typeof config === 'string') {
          // static $resource() { return 'my-custom-element-name' }
          // though rare case, still needs to handle properly
          config = { name: config };
        }
        // after normalization, copy to another obj
        // as the config could come from a static field, which subject to later reuse
        // it shouldn't be modified
        config = Object.assign({}, config);
        // no type specified = custom element
        let resourceType = config.type || 'element';
        // cannot do name = config.name || target.name
        // depends on resource type, it may need to use different strategies to normalize name
        let name = config.name;
        switch (resourceType) { // eslint-disable-line default-case
        case 'element': case 'attribute':
          // if a metadata is supplied, use it
          resource = existing || new HtmlBehaviorResource();
          resource[conventionMark] = true;
          if (resourceType === 'element') {
            // if element name was defined before applying convention here
            // it's a result from `@customElement` call (or manual modification)
            // need not to redefine name
            // otherwise, fall into following if
            if (!resource.elementName) {
              resource.elementName = name
                ? validateBehaviorName(name, 'custom element')
                : _hyphenate(target.name);
            }
          } else {
            // attribute name was defined before applying convention here
            // it's a result from `@customAttribute` call (or manual modification)
            // need not to redefine name
            // otherwise, fall into following if
            if (!resource.attributeName) {
              resource.attributeName = name
                ? validateBehaviorName(name, 'custom attribute')
                : _hyphenate(target.name);
            }
          }
          if ('templateController' in config) {
            // map templateController to liftsContent
            config.liftsContent = config.templateController;
            delete config.templateController;
          }
          if ('defaultBindingMode' in config && resource.attributeDefaultBindingMode !== undefined) {
            // map defaultBindingMode to attributeDefaultBinding mode
            // custom element doesn't have default binding mode
            config.attributeDefaultBindingMode = config.defaultBindingMode;
            delete config.defaultBindingMode;
          }
          // not bringing over the name.
          delete config.name;
          // just copy over. Devs are responsible for what specified in the config
          Object.assign(resource, config);
          break;
        case 'valueConverter':
          resource = new ValueConverterResource(camelCase(name || target.name));
          break;
        case 'bindingBehavior':
          resource = new BindingBehaviorResource(camelCase(name || target.name));
          break;
        case 'viewEngineHooks':
          resource = new ViewEngineHooksResource();
          break;
        }
      }

      if (resource instanceof HtmlBehaviorResource) {
        // check for bindable registration
        // This will concat bindables specified in static field / method with bindables specified via decorators
        // Which means if `name` is specified in both decorator and static config, it will be duplicated here
        // though it will finally resolves to only 1 `name` attribute
        // Will not break if it's done in that way but probably only happenned in inheritance scenarios.
        let bindables = typeof config === 'string' ? undefined : config.bindables;
        let currentProps = resource.properties;
        if (Array.isArray(bindables)) {
          for (let i = 0, ii = bindables.length; ii > i; ++i) {
            let prop = bindables[i];
            if (!prop || (typeof prop !== 'string' && !prop.name)) {
              throw new Error(`Invalid bindable property at "${i}" for class "${target.name}". Expected either a string or an object with "name" property.`);
            }
            let newProp = new BindableProperty(prop);
            // Bindable properties defined in $resource convention
            // shouldn't override existing prop with the same name
            // as they could be explicitly defined via decorator, thus more trust worthy ?
            let existed = false;
            for (let j = 0, jj = currentProps.length; jj > j; ++j) {
              if (currentProps[j].name === newProp.name) {
                existed = true;
                break;
              }
            }
            if (existed) {
              continue;
            }
            newProp.registerWith(target, resource);
          }
        }
      }
    }
    return resource;
  }