mudgen / webscript

Webscript is a Javascript library for creating DOM elements. Use it to create web applications. It is like HTML but it is Javascript. It is designed to work with existing libraries.
https://mudgen.github.io/webscript/docs/
MIT License
86 stars 9 forks source link

TypeError: img(...).src is not a function in test/app.js #8

Closed clsource closed 4 years ago

clsource commented 4 years ago

Using the code from the latest version the example throws error.

test/app.js

import builders from "../src/webscript.js";
import createElement from "../src/createDOMElement.js";

const { body, div, p, span, img } = builders(createElement);

let classes;

const app = body`flex items-center justify-center h-screen`(
  div`max-w-sm rounded overflow-hidden shadow-lg`(
    img`w-full`.src`img/card-top.jpg`.alt`Sunset in the mountains`,
    div`px-6 py-4`(
      div`font-bold text-xl mb-2`("The Coldest Sunset"),
      p`text-gray-700 text-base`(
        " Lorem ipsum dolor sit amet, consectetur adipisicing ..."
      )
    ),
    div`px-6 py-4 text-sm font-semibold text-gray-700`(
      span`${(classes =
        "inline-block bg-gray-200 rounded-full px-3 py-1")} mr-2`(
        "#photography"
      ),
      span`${classes} mr-2`("#travel"),
      span`${classes}`("#winter")
    )
  )
);

document.body = app;

This seems to be related to the new webscript implementation since it returns a rendered version on the first call

console.log(img`w-full`);

returns <img>

if we change the webscript to version 0.04

function createElement(tagName, props, ...children) {
  tagName = tagName.toLowerCase();
  const element = ["svg", "path", "title"].includes(tagName)
    ? document.createElementNS("http://www.w3.org/2000/svg", tagName)
    : document.createElement(tagName);
  for (let key in props) {
    let value = props[key];
    if (typeof value === "string") {
      if (key === "className") {
        key = "class";
      }
      element.setAttribute(key, value);
    } else {
      element[key] = value;
    }
  }
  for (const child of children) {
    if (
      typeof child === "number" ||
      typeof child === "boolean" ||
      child instanceof Date ||
      child instanceof RegExp
    ) {
      element.append(String(child));
    } else {
      element.append(child);
    }
  }
  return element;
}

function joinStringsAndArgs(args) {
  const [strings, ...templateArgs] = args;
  const result = [];
  for (const [index, s] of strings.entries()) {
    result.push(s);
    result.push(templateArgs[index]);
  }
  return result.join("");
}

function elementBuilderBuilder(elementConstructor, element) {
  function getPropertyValue(...args) {
    let [first] = args;
    if (typeof first === "undefined") {
      first = "";
    } else if (Array.isArray(first) && Object.isFrozen(first)) {
      first = joinStringsAndArgs(args);
    }
    let { props, prop } = this.__element_info__;
    props = { [prop]: first, ...props };
    return elementBuilder({ props, prop: null });
  }
  function getPropsValues(props) {
    let { props: existingProps } = this.__element_info__;
    props = { ...props, ...existingProps };
    return elementBuilder({ props, prop: null });
  }
  function elementBuilder(propsInfo) {
    let builder = new Proxy(() => {}, {
      apply(target, thisArg, args) {
        let [first] = args;
        if (Array.isArray(first) && Object.isFrozen(first)) {
          let first = joinStringsAndArgs(args).trim();
          let value = first.split(/[\s.]+/);
          let newProps = {};
          if (value.length > 0) {
            if (value[0].startsWith("#")) {
              newProps["id"] = value.shift().slice(1);
            }
          }
          if (value.length > 0) {
            newProps["className"] = value.join(" ");
          }
          let { props } = builder.__element_info__;
          props = { ...newProps, ...props };
          return elementBuilder({ props, prop: null });
        } else {
          for (let i = 0; i < args.length; i++) {
            let arg = args[i];
            if (typeof arg === "function" && arg.__element_info__) {
              args[i] = arg();
            }
          }
          let { props } = builder.__element_info__;
          return elementConstructor(element, props, ...args);
        }
      },
      get(target, prop) {
        const result = target[prop];
        if (typeof result !== "undefined") {
          return result;
        }
        if (prop === "props") {
          return getPropsValues;
        } else if (typeof prop === "string") {
          if (prop.startsWith("data")) {
            prop = prop.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
          }
          // @ts-ignore
          target.__element_info__.prop = prop;
          return getPropertyValue;
        }
      },
      set(target, prop, value) {
        target[prop] = value;
        return true;
      },
    });
    builder.__element_info__ = propsInfo;
    return builder;
  }
  return elementBuilder({ props: {}, prop: null });
}

function elementBuildersBuilder(
  elementConstructor = createElement,
  elements = []
) {
  if (
    Object.prototype.toString.call(elementConstructor) === "[object Object]"
  ) {
    elementConstructor =
      elementConstructor["elementConstructor"] || createElement;
    elements = elementConstructor["elements"] || [];
  }
  elementConstructor = elementConstructor || createElement;
  if (elements.length > 0) {
    let builders = [];
    for (const element of elements) {
      builders.push(elementBuilderBuilder(elementConstructor, element));
    }
    return builders;
  } else {
    return new Proxy(() => {}, {
      apply(target, thisArg, args) {
        return elementBuildersBuilder(...args);
      },
      get(target, prop) {
        const result = target[prop];
        if (typeof result !== "undefined") {
          return result;
        }
        target[prop] = elementBuilderBuilder(elementConstructor, prop);
        return target[prop];
      },
    });
  }
}

const elementBuilders = elementBuildersBuilder();
export default elementBuilders;

Then executing

console.log(img`w-full`);

returns Proxy

mudgen commented 4 years ago

Thank you for noticing this.

This is happening because there was a major change to the API.

Instead of

img`w-full`;

It is now:

img.class`w-full`;

I made this change because I learned from experience working with Webscript that it is better for it to work consistently.

Tagged Template Literal syntax is another way to call a function with strings.
So for example these two produce the same result:

p`This is a paragraph`;
p("This is a paragraph");

I don't expect such major changes to the API to happen anymore.