OrganicJS by idibidiart

Super Light Framework for Reusable JS+HTML+SVG Components (135 lines of code including comments)

Click on each box and use corresponding slider Each widget is derived from the same HTML/SVG/JS building blocks yet each has different properties and behavior...

Below is the code for creating this app (the components themselves are defined in bouncy.js)


<!-- Define widgets with nestable, reusable HTML5/SVG fragments for in-place cloning and rendering -->

<!-- HTML widgets container-->
<div>
    <!-- HTML fragment -->
    <div frag="sliderWidget">
        <input type="range" />
    </div>
</div>

<!--- SVG widgets container -->
<svg xmlns="http://www.w3.org/2000/svg"
     preserveAspectRatio="xMinYMin meet"
     viewbox="0 0 960 960"
     height="55%">
    <!--- SVG fragment -->

    <text x="20" y="100" font-size = "72">Click on each box and use corresponding slider</text>
    <g frag="demoWidget" transform="translate(0, 100)">
        <!--- nested SVG fragment -->
        <rect frag="board" />
        <!--- nested SVG fragment -->
        <circle frag="ball" />
    </g>
    <g>
        <text x="20" y="840" font-size = "48">Each widget is derived from the same HTML/SVG/JS building blocks
            yet each has different properties and behavior...</text>
    </g>

</svg>

<!-- NOTE:

Don't use id's on elements. They are bad because:

   1. they pollute the global window namespace
   2. elements which reuse an id, e.g. clones, go missing (with no warning)

To substitute for DOM element id's, OrganicJS provides universally unique IDs (as attributes) for every element
created from a fragment (via app.new()) and a corresponding app.getElementById() method
-->


<script>

    // Notes
    //
    // The in-line script shows how to build the app from components using predefined HTML/SVG markup
    // and how to access each component's public properties and methods. Hooking up application level unit
    // tests would make a nice extension to this example

    (function() {

        //see organic.js
        var     model = app.model()
            ,   mainWidget = []
            ,   sliderWidget = []
            ,   numWidgets = 3;

        // All widgets in this example are derived from the above-defined HTML/SVG fragments
        // We may iteratively create the required number of widget instances and configure their properties/methods
        for (var n = 0; n < numWidgets; n++) {

            sliderWidget[n] = {};

            // clone slider widget in place
            sliderWidget[n].sliderWidget_id = app.new($('[frag="sliderWidget"]'))

            // you may use the cloned slider with jQuery or any other library as you would normally, e.g.:
            // $(app.getElementById(sliderWidget[n].slider_id))).attr("class", "xyz")
            // $(app.getElementById(sliderWidget[n].slider_id))).css({display: "block", etc })

            sliderWidget[n].slider = app.slider(model)
                    //settings
                    .elem($('input', app.getElementById(sliderWidget[n].sliderWidget_id)))
                    .left(100)
                    .top(20)
                    .display("block")
                    .min("10")
                    .max("50")
                    .value("10")
                    // method that takes a function and $.binds it to the element's change event
                    .onChange((function(m) { return function() {

                        var that = this;

                        if (m == 0) {
                            mainWidget[m].ball1
                                .radius(function() {
                                    return +that.value + (that.value > 3 ? 0 : 3);
                                })
                                .render()
                        }
                        if (m == 1) {
                            mainWidget[m].ball1
                                .fill(function () {return '#'+Math.ceil((Math.random())*16777215).toString(16)})
                                .render()
                            mainWidget[m].ball2
                                .fill(function () {return '#'+Math.ceil((Math.random())*16777215).toString(16)})
                                .render()
                        }
                        if (m == 2) {
                            mainWidget[m].ball1.speed(this.value / 8)
                            mainWidget[m].ball2.speed(this.value / 13)
                            mainWidget[m].ball3.speed(this.value / 21)
                        }
                    }})(n))
                    .render();
        }

        for (var n = 0; n < numWidgets; n++) {

            mainWidget[n] = {}

            // clone widget in place
            mainWidget[n].widget_id =  app.new($('[frag="demoWidget"]'))

            // create clone of board fragment _inside_ the cloned widget fragment
            mainWidget[n].board_id = app.new($('[frag="board"]', app.getElementById(mainWidget[n].widget_id)))

            // create clone of ball #1 _inside_ the cloned widget
            mainWidget[n].ball1_id = app.new($('[frag="ball"]', app.getElementById(mainWidget[n].widget_id)))
            // create clone of balls #2 & #3 _inside_ the cloned widget
            if (n >= 1) mainWidget[n].ball2_id = app.new($('[frag="ball"]', app.getElementById(mainWidget[n].widget_id)))
            if (n >= 2) mainWidget[n].ball3_id = app.new($('[frag="ball"]', app.getElementById(mainWidget[n].widget_id)))
            // you may use the cloned ball with jQuery or any other library as you would normally, e.g.:
            // $(app.getElementById(mainWidget[n].ball2_id))).attr("class", "xyz")
            // $(app.getElementById(mainWidget[n].ball3_id))).css({display: "block", etc })

            mainWidget[n].board = app.board(model)
                    //settings
                    .elem($(app.getElementById(mainWidget[n].board_id)))
                    .top(100)
                    .left((function(m) {
                            // we may return a function that evaluates the code below if we wish for .left() to be
                            // set dynamically every time we call .left()
                            // in that case, 'this' would be evaluated as the component instance
                            if (m == 0) return 100;
                            return mainWidget[m-1].board.left() + mainWidget[m-1].board.width() + 20
                    })(n))
                    .width(500)
                    .height(500)
                    .fill((function(m) { return function () {
                        switch (m) {
                            case 0:
                                return "#F1D848";
                            case 1:
                                return "#FCACD1"
                            case 2:
                                return "#77CDF2"
                        }
                    }})(n))
                    .onClick((function(m) { return function () {
                        if (!mainWidget[m].ball1.animating()) {
                            mainWidget[m].ball1.animate()
                            if (m >= 1) mainWidget[m].ball2.animate()
                            if (m >= 2) mainWidget[m].ball3.animate()
                        } else {
                            // mainWidget[n].ball1.kick()
                        }
                    }})(n))
                    .render();

            mainWidget[n].ball1 = app.ball(model)
                    // function-scoped import
                    .import([{comp: mainWidget[n].board, into: 'animate'}])
                    // settings
                    .elem(app.getElementById(mainWidget[n].ball1_id))
                    .radius(10)
                    .dx(2)
                    .dy(4)
                    .speed(1.5)
                    .x(mainWidget[n].board.left() + (mainWidget[n].board.width() / 2))
                    .y(mainWidget[n].board.top() + (mainWidget[n].board.height() / 2))
                    .fill("red")
                    .render();

            if (n >= 1) {
                    mainWidget[n].ball2 = app.ball(model)
                    .import([{comp: mainWidget[n].board, into: 'animate'}])
                    // settings
                    .elem(app.getElementById(mainWidget[n].ball2_id))
                    .radius(mainWidget[n].ball1.radius() * 2)
                    .dx(6)
                    .dy(12)
                    .speed(1.5)
                    .x(mainWidget[n].board.left() + (mainWidget[n].board.width() / 3))
                    .y( mainWidget[n].board.top() + (mainWidget[n].board.height() / 2))
                    .fill("blue")
                    .render();
            }

            if (n >= 2) {
                    mainWidget[n].ball3 = app.ball(model)
                    .import([{comp: mainWidget[n].board, into: 'animate'}])
                    // settings
                    .elem(app.getElementById(mainWidget[n].ball3_id))
                    .radius(mainWidget[n].ball1.radius() * 2.5)
                    .dx(8)
                    .dy(14)
                    .speed(1.5)
                    .x(mainWidget[n].board.left() + (mainWidget[n].board.width() / 4.5))
                    .y(mainWidget[n].board.top() + (mainWidget[n].board.height() / 2))
                    .fill("green")
                    .render()
            }
        }

    } ())

</script>