Git Product home page Git Product logo

lwc2014's Introduction

Language Workbench Challenge 2014

This repository holds the reference implementation for the semantics of the QL and QLS languages, as defined in the assignment. Specifically, it implements the example questionnaire shown in the assignment.

Status

Status of implementation: released, but extendable

Particular TODOs:

  • Styling of widgets, through web/css/QL.css.
  • Implementation of value widgets specifically for QLS: either new value widgets, or add styling options to existing ones
  • More documentation of the framework, also as JSdoc.

Disclaimer

The reference implementation is provided as-is. The LWC programming committee retains the right to change and expand the code throughout the following months. However, the committee will try to keep the API stable.

Repository organization

The repository contains (currently) one folder holding an Eclipse JavaScript project reference-implementation with the implementation. It's not necessary to use Eclipse: you can view the project folder as the root for development. Inside the Eclipse project, the folder web contains everything that's relevant: the rest is purely for setup or Eclipse configuration.

Contributing

Participants of the Challenge are invited to contribute to this reference implementation, by the usual means:

  • forking this repository and creating pull requests
  • filing issues

It's especially appreciated if participants come up with implementations of value widgets for QLS.

The example

The example in the QL challenge text is implemented in web/js/examples/exampleForm-impl.js. It is executed by running web/QL_example.html in any modern browser. The example uses the "QL(S) run time(s)", i.e. the framework JS files located in web/js/framework/.

Generation vs. interpretation

The implementation of the example questionnaire fits the code generation paradigm in a natural in the sense that it builds up the necessary code corresponding to the example model directly in JavaScript, instead of interpreting a data structure. However, the interpretative style should be equally simple to realize, depending on the concrete JavaScript syntax of the model.

The framework

Characteristics:

  • Technology: HTML5, i.e. HTML+JavaScript+CSS
  • Widget-based: the input elements render into the DOM and encapsulate required logic
  • Reactive: input elements signal when their input has changed, so changes can be propagated

All framework objects (functions) are properties of the global QLrt (QL run time) object. In the rest of the text, the QLrt. prefix is assumed to be implicit when referencing types.

Structural widgets

There are 3 widgets which are structural in the sense that they define the form and its structure, but don't say anything about the values in it.

  • FormWidget: The base widget for one whole form, acting as a controller for it. It has ConditionalGroupWidgets and SimpleFormElementWidgets as children.
  • SimpleFormElementWidget: Corresponds to a label and a value (passed as an appropriate value widget) in the UI. It composes a value widget, but not other widgets.
  • ConditionalGroupWidget: Corresponds to a grouping of other widgets, where the visibility and applicability of the whole group is governed by an expression. It can have other ConditionalGroupWidgets and SimpleFormElementWidgets as children.

Both ConditionalGroupWidget and SimpleFormElementWidget are instances (sub types) of Child.

API

The FormWidget constructor takes a settings object with a String name and a callback function submitCallback. If the form is complete, as defined by all its children being defined, then the submit button is enabled and, when clicked, the callback function is called with an object holding the form data.

FormWidget has the following API methods:

  • domElement: returns the DOM element containing the whole form tree;
  • activate: activates the form widget, i.e. makes it visible in a valid state.

The SimpleFormElementWidget constructor takes a settings object with the following:

  • a required String label, used in the UI;
  • a required value widget object valueWidget;
  • a String name that's only required when the value widget is not computed - the name is only used for composing the form data object.

The SimpleFormElementWidget and ConditionalGroupWidget widgets both have one API method: appendTo appends the widget itself to a parent widget, returning itself for chaining.

The ConditionalGroupWidget constructor takes a required lazy value which governs when the widgets composed by the instance are visible/in scope.

Value widgets

Value widgets deal with showing values and either inputting or computing those. These objects are both widgets and value wrappers: they are rendered as well as provide an interface to get and set their value, and inspect whether they have been set.

API

A value widget is JS constructor, taking an optional LazyValue argument - lazy values are discussed below. This is actually the only API you need to know about to build forms.

If a lazy value is given, the form element is regarded to be computed which results in the input element being disabled and the value not passed into the form data object upon submitting the form.

Rolling your own

For both QL and especially QLS we need several value widgets. To create your own, follow the following template (fill in the TODOs):


QLrt.MyValueWidget = function (lazyValue) {

	QLrt.BaseValueWidget.call(this, lazyValue);

	this.createElement = function () {
		return /* TODO  a jQuery-wrapped DOM element holding the tree for the widget */;
	};

	this.setValue = function (val) {
		/* TODO  set value of this.domElement() */;
	};

	this.valueInternal = function () {
		return /* TODO  the value retrieved from this.domElement(), DISREGARDING definedness logic */;
	};

	this.definedInternal = function () {
		return /* TODO  whether this.valueInternal() represents a defined value */;
	};

}
QLrt.MyValueWidget.prototype = Object.create(QLrt.BaseValueWidget.prototype);

Lazy values

To make implementing the reactive behavior of the form and dealing with the semantics of undefined values as simple as possible, we provide a LazyValue class, instances of which can be asked to be evaluated against the current state of the form.

LazyValue is a contructor that takes a dependent values function, an expression function and an optional funky boolean. The dependent values function must return an array of value widget objects - remember that these also serve as value wrappers. The expression function takes arguments corresponding to the array returned by the dependent values function and returns a value or undefined in case a dependent value is currently undefined. The funky boolean indicates whether a dependent value being undefined does not mean that the expression's value will be undefined.

As an example, consider the computation of sellingPrice - privateDebt. In our example's code, this becomes:

1.	var sellingPrice = (new QLrt.SimpleFormElementWidget({ … })).appendTo(group1);
2.	var privateDebt = (new QLrt.SimpleFormElementWidget({ … })).appendTo(group1);
3.	(new QLrt.SimpleFormElementWidget({ label: "Value residue:", valueWidget: new QLrt.MoneyValueWidget(new QLrt.LazyValue(
4.			function () { return [ sellingPrice, privateDebt ]; },
5.			function (sellingPrice, privateDebt) { return sellingPrice - privateDebt; }
6.		)) })).appendTo(group1);

Line 4 is the dependent values function which returns the value widget objects for sellingPrice and privateDebt. Line 5 is the expression function.

Note that sellingPrice and/or privateDebt might be undefined. However, since this lazy value is "non-funky" (3rd parameter not present, thus not set to true), first all dependent (wrapped) values are checked for definedness. If they're not all defined, the expression evaluates to undefined. One only has to use "funky" when for some combinations/ranges of input, it doesn't matter whether a value is undefined, (e.g. using shortcutting boolean operators, and/or in pathological cases such as multiplying with 0), or when some expression explicitly depends on an input being defined or not.

The main reason for using a dependent values function, instead of direct references to variables, is that it relieves the form builder of the need to make sure widgets are instantiated before they're referenced in an expression function. This is particularly necessary because of the variable hoisting that JS does. The other reason is that it makes the expression function's code look much better, because it's not (always) necessary to test for definedness and postfix with .value(). Also, this gives the code a sort of a monadicky feel :)

lwc2014's People

Contributors

delphino avatar dslmeinte avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar

lwc2014's Issues

Section and plain text widgets

In the framework it is not clear how to represent a paragraph of plain text or a section with a heading. The former would be useful to show the final guess in the Binary Search Questionnaire assignment, while the latter is needed to implement the QLS part of the base assignment.

Since I'm not a JavaScript/DOM expert I developed the following additional widgets to be included in the framework, but I would be equally happy if you could provide an example to mimic the same behavior using the standard widget set.

QLrt.SectionWidget = function (settings) {
    QLrt.Child.call(this);
    if (typeof (settings) !== 'object' || settings.label === undefined) {
        throw 'invalid or incomplete settings';
    }
    var container = QLrt.mk('section').append(QLrt.mk('h1').text(settings.label));
    this.domElement = function () {
        return container;
    };
    var children = [];
    this.append = function (widget) {
        children.push(widget);
        container.append(widget.domElement());
    };
    this.update = function () {
        _.each(children, function (subWidget) {
            subWidget.update();
        });
    };
    this.defined = function () {
        return _.all(children, function (subWidget) {
            return subWidget.defined();
        });
    };
    this.asJSON = function () {
        var result = {};
        _.each(children, function (subWidget) {
            _.extend(result, subWidget.asJSON());
        });
        return result;
    };
};
QLrt.SectionWidget.prototype = Object.create(QLrt.Child.prototype);
QLrt.TextWidget = function (settings) {
    QLrt.Child.call(this);
    if (typeof (settings) !== 'object' || settings.label === undefined) {
        throw 'invalid or incomplete settings';
    }
    var container = QLrt.mk('p').text(settings.label);
    this.domElement = function () {
        return container;
    };
    this.update = function () {};
    this.defined = function () {};
    this.asJSON = function () {
        var result = {};
        return result;
    };
};
QLrt.TextWidget.prototype = Object.create(QLrt.Child.prototype);

BooleanValueWidget can't cope with binary search task

The BooleanValueWidget's checkbox only has two states, checked and unchecked. We also need it to have a separate "as yet unanswered" state, so that we can distinguished between unanswered (which will not reveal any dependent questions), unchecked (which will show the 'IF false' dependent question), and checked (which will show the "IF true" dependent question).

A simple option is to represent a Boolean question with two radio buttons, Yes and No, with neither initially selected. Although that doesn't allow returning an answer to "as yet unanswered", that's not something that's necessary in the focus assignment.

(Of course we can already get the desired binary tree questionnaire flow by creatively using Enum questions, but that is a hack - the domain has Boolean questions, so the model should have Boolean questions.)

Binary search numbers incorrect

In focus assignment/scalability and teamwork.md, the numbers are mostly off by one, and should be 1..1024, "between 1 and 512" (inclusive), "1..256", "513..1024". Actually, the last one is off by even more, and in this context should actually be "513..768": if the number is not in 1..512, we already know it is in 513..1024, so the next question should only ask about the lower half of that range.

Binary Search Questionnaire disambiguation

By implementing the Binary Search Questionnaire assignment we discovered a corner case that should be clarified to ensure that all the implementations produce the same questionnaire.

More specifically, in every generated questionnaire, regardless of the interval of numbers chosen, there will be always cases in which the user will eventually has to choose between two numbers, say n and n+1.

In this specific case I would suggest to generate a question in the form of "is the answer n?" rather than the more cumbersome "is the answer between n and n ?".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.