Creating javascript objects within QML file using imported javascript resource



  • I'm trying to import a JavaScript file (for implementing the Critical Path Method) in a Qt Quick project. The JavaScript code I'm using can be found here:

    https://gist.github.com/perico/7790396#file-cpm-js

    This code contains some examples for creating a network and determining its critical path. The first thing I tried is to see if I could import this file, and within a QML file, create one of the example network activities:

    var table = new CPM.ActivityList();
    table.addActivity(new Activity({
            id: 'A',
            duration: 8,
     }));
    

    The error is:
    Expected token `:'

    I'm unsure whether I should use other syntax within the QML file or even whether it is possible to use this JavaScript file for creating a network of activities (objects within objects).

    The main.qml file:

    import QtQuick 2.7
    import QtQuick.Controls 1.5
    import "cpm.js" as CPM
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        menuBar: MenuBar {
            Menu {
                title: qsTr("File")
                MenuItem {
                    text: qsTr("&Open")
                    onTriggered: console.log("Open action triggered");
                }
                MenuItem {
                    text: qsTr("Exit")
                    onTriggered: Qt.quit();
                }
            }
        }
    
        Label {
            text: qsTr("Hello World")
            anchors.centerIn: parent
        }
    
        var table = new CPM.ActivityList();
        table.addActivity(new Activity({
            id: 'A',
            duration: 8,
        }));
    }
    

    The cpm.js file:

    /**
     * Implementation of the Critical Path Method (CPM) with variation
     * @see http://en.wikipedia.org/wiki/Critical_path_method
     *
     * Shows all the critical Paths, returns a subset of the graph
     * containing the critical activities
     */
    
    /**
     * Activity Class
     * XE: new Activity({
     *		id: 'A',
     *		duration: 8,
     *      	predecessors: ['C','B']
     *	});
     */
    function Activity(configs) {
        var self = this;
        configs = configs || {};
        self.id = configs.id;
        self.duration = configs.duration;
        self.est = configs.est; //Earliest Start Time
        self.lst = configs.lst; //Latest Start Time
        self.eet = configs.eet; //Earliest End Time
        self.let = configs.let; //Latest End Time
        self.h 	 = configs.h; //clearance (holgura)
        self.successors = [];
        self.predecessors = configs.predecessors || [];
        return self;
    }
    
    /**
     * List of Activities Class
     *
     */
    function ActivityList() {
        //Private vars
        var self = this;
        var processed = false;
        var list = {};
        var start, end;
    
        /**
         * Add an activity to the list
         */
        self.addActivity = function (activity) {
            list[activity.id] = activity;
        };
    
        /**
        * Private method Pre process list
        * Adds and sets the start and end node
        * Replaces successors and predecessors Ids for pointers
        * @throws Error if a predecessor not exists
        */
        function processList(){
            if(processed){
                return; //Already processed
            }
            processed = true;
    
            start = new Activity({id : 'start', duration : 0});
    
            //Replaces id for pointers to actvities
            for(var i in list){
                var current = list[i];
                var predecessorsIds = current.predecessors;
                var predecessors = [];
    
                if(predecessorsIds.length == 0){
                    predecessors.push(start);
                    start.successors.push(current);
                }else{
                    for(var j in predecessorsIds){
                        var previous = list[predecessorsIds[j]];
                        if(!previous){
                            throw new Error('Node ' + j + ' dont exists');
                        }
                        predecessors.push(previous);
                        previous.successors.push(current);
                    }
                }
                current.predecessors = predecessors;
            }
    
        }
    
        /**
         * Private function set Earliest Times
         * Earliest Start Time (est) and Earliest End Time (eet)
         * Recursively browse starting from root
         */
        function setEarlyTimes(root){
            for(var i in root.successors){
                var node = root.successors[i];
    
                var predesessors = node.predecessors;
                for (var j in predesessors) {
                    var activity = predesessors[j];
                    if (node.est == null || node.est < activity.eet)
                        node.est = activity.eet;
                }
                node.eet = node.est + node.duration;
                setEarlyTimes(node);
            }
        }
    
    
        /**
         * Private function set Latest Times
         * Latest Start Time (est) and Latest End Time (eet)
         * Recursively browse starting from root
         */
        function setLateTimes(root){
            if(root.successors.length == 0){
                root.let = root.eet;
                root.lst = root.let - root.duration;
                root.h   = root.eet - root.let;
            }else{
                for(var i in root.successors){
                    var node = root.successors[i];
                    setLateTimes(node);
                    if(root.let == null || root.let > node.lst){
                        root.let = node.lst;
                    }
                }
    
                root.lst = root.let - root.duration;
                root.h = root.let - root.eet;
            }
        }
    
    
        /**
         * Build Critical Path Tree recursively
         *
         */
        function buildCriticalPath(root, path){
            if(root.h == 0){
                var predecessors = root.predecessors;
                for(var i in predecessors){
                    var node = predecessors[i];
                    if(node.h == 0){
                        var clone = new Activity({
                            id : node.id,
                            duration : node.duration,
                            est : node.est,
                            lst : node.lst,
                            eet : node.eet,
                            let : node.let,
                            h : node.h
                        });
                        //Dont add start ... its just a fake node
                        if(node !== start){
                            path.predecessors.push(clone);
                            buildCriticalPath(node, clone);
                        }
    
                    }
                }
            }
    
        }
    
        /**
         * Applies the PEM, with little modification
         * Returns a Graph subset with the structure of the involved activities
         */
        self.getCriticalPath = function(endid){
            if(!endid){
                throw new Error('End activity id is required!');
            }
            end = list[endid];
            if(!end){
                throw new Error('Node end dont not match');
            }
            //Make sure that list is well defined
            processList();
    
            //Setup Start Times
            start.est = 0;
            start.eet = 0;
            setEarlyTimes(start);
    
            //Setup End Times
            end.let = end.eet;
            end.lst = end.let - end.duration;
            end.h  	= end.eet - end.let;
            setLateTimes(start);
    
            //Assemble Critical Path (tree)
            var path = null;
            if(end.h == 0){
                var path = new Activity({
                    id : end.id,
                    duration : end.duration,
                    est : end.est,
                    lst : end.lst,
                    eet : end.eet,
                    let : end.let,
                    h	: end.h
                });
    
                buildCriticalPath(end, path);
            }
            return path;
        };
    
        /**
         * Get the activity list, does a preprocessing (only once)
         */
        self.getList = function () {
            processList();
            return list;
        };
        return self;
    };
    
    
    //########################## Example 1 ######################
    
    //var table = new ActivityList();
    //table.addActivity(new Activity({
    //    id: 'A',
    //    duration: 8,
    //}));
    
    //table.addActivity(new Activity({
    //    id: 'B',
    //    duration: 3,
    //}));
    
    //table.addActivity(new Activity({
    //    id: 'C',
    //    duration: 1,
    //    predecessors: ['A', 'B'],
    //}));
    
    //table.addActivity(new Activity({
    //    id: 'D',
    //    duration: 6,
    //    predecessors: ['C','B'],
    //}));
    
    //table.addActivity(new Activity({
    //    id: 'E',
    //    duration: 4,
    //    predecessors: ['D','C','F','G'],
    //}));
    
    //table.addActivity(new Activity({
    //    id: 'F',
    //    duration: 18,
    //    predecessors: ['B'],
    //}));
    
    //table.addActivity(new Activity({
    //    id: 'G',
    //    duration: 10,
    //    predecessors: ['A','C'],
    //}));
    
    //console.log('TABLE', table.getList());
    
    //var path = table.getCriticalPath('E');
    
    ////RETURNS E->F->B
    //console.log(path);
    
    
    
    //########################## Example 2 ######################
    /**
     * A -> C -> D
     *     /
     *    B
     */
    //var table2 = new ActivityList();
    //table2.addActivity(new Activity({
    //    id: 'A',
    //    duration: 1,
    //}));
    
    //table2.addActivity(new Activity({
    //    id: 'B',
    //    duration: 1,
    //    predecessors: [],
    //}));
    
    //table2.addActivity(new Activity({
    //    id: 'C',
    //    duration: 3,
    //    predecessors: ['A', 'B'],
    //}));
    
    //table2.addActivity(new Activity({
    //    id: 'D',
    //    duration: 6,
    //    predecessors: ['C'],
    //}));
    
    
    
    //console.log('TABLE 2', table2.getList());
    
    //var path = table2.getCriticalPath('D');
    ///*
    // * RETURNS D->C->A
    // *	       \
    // *		B
    //*/
    //console.log(path);
    

  • Moderators

    @AimedSquid That is because the JavaScript cannot be execute in that scope.
    Please refer this document:
    http://doc.qt.io/qt-5/qtqml-javascript-expressions.html

    For eg. the error can be solved by calling the JS functions in Component.onCompleted handler

    Component.onCompleted:  {
        var table = new CPM.ActivityList();
        table.addActivity(new CPM.Activity({
                           id: 'A',
                           duration: 8,
                       }));
    }
    


  • Thanks again p3c0! This would allow me to create the table of activities, add activities to it and determine the critical path . I've gone over the document you referred to but I'm (again) struggling with how to dynamically add activities to the table after the component is instantiated.

    I have updated the code with a button (button1) to add individual activities to the table and another button to determine the critical path as soon as all activities have been added.

    An empty table is created within the Component.onCompleted handler:

    Component.onCompleted:  {
        var table = new CPM.ActivityList();
    }
    

    (I'm assuming that this Component.onCompleted handler relates to the ApplicationWindow.)

    I'm then trying to use button1's onClicked event to add an activity to the table using the addActivity function as follows:

    onClicked: mainwindow.table.addActivity(new CPM.Activity({id: textField1.text, duration: textField2.text, }));
    

    This then gives me the following error:

    TypeError: Cannot call method 'addActivity' of undefined

    I guess I'm still rather struggling with the scope of JavaScript functions. I cannot find something similar in the document you refer to, could you (or other kind members) tell me how to procede?

    The main.qml file:

    import QtQuick 2.7
    import QtQuick.Controls 1.5
    import "cpm.js" as CPM
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
        id: mainwindow
    
        menuBar: MenuBar {
            Menu {
                title: qsTr("File")
                MenuItem {
                    text: qsTr("&Open")
                    onTriggered: console.log("Open action triggered");
                }
                MenuItem {
                    text: qsTr("Exit")
                    onTriggered: Qt.quit();
                }
            }
        }
    
        Label {
            text: qsTr("id")
            anchors.verticalCenterOffset: -182
            anchors.horizontalCenterOffset: 1
            anchors.centerIn: parent
        }
    
        TextField {
            id: textField1
            x: 269
            y: 80
            placeholderText: qsTr("Text Field")
        }
    
        TextField {
            id: textField2
            x: 269
            y: 162
            placeholderText: qsTr("Text Field")
        }
    
        Label {
            x: -4
            y: 2
            text: qsTr("duration")
            anchors.centerIn: parent
            anchors.verticalCenterOffset: -108
            anchors.horizontalCenterOffset: 0
        }
    
        Button {
            id: button1
            x: 280
            y: 211
            text: qsTr("Add activity")
            onClicked: mainwindow.table.addActivity(new CPM.Activity({id: textField1.text, duration: textField2.text, }));
    
        }
    
        Button {
            id: button2
            x: 249
            y: 365
            text: qsTr("Determine critical path")
            // code for determining critical path will go here
        }
    
        Component.onCompleted:  {
            var table = new CPM.ActivityList();
        }
    }
    

  • Moderators

    @AimedSquid

    Component.onCompleted:  {
           var table = new CPM.ActivityList();
    }
    

    The scope of table is limited to that function.
    You can instead create this on onClicked instead.



  • Thanks again for your support p3c0!

    I understand that I could create the table on the onClicked event of button1. However, as far as I understand, a new table would then be created each time the user clicks that button.

    What I need is a way to create the table, then each time the user fills in the text fields to define a new activity, this activity should be added to the table of activities by pressing button1. Then, after all activities have been created, button2 is used to call another function to determine the critical path of the network as defined in the table.

    This would mean that the table 'object' should created first, then be accessible by the different button objects for 1) adding activities and 2) determining the critical path. Is this feasible?



  • @AimedSquid You should use property to solve your problem

    ...
    id: mainwindow
    
    readonly property var table: new CPM.ActivityList()
    ...
    
    

    At this case you could use construction mainwindow.table from any point of the window scope.


  • Moderators

    @AimedSquid It was just to explain the problem. As @Roumed said you can create a global property and then assign the instance to this property and so that it could be accessible.



  • @p3c0 and @Roumed Thanks for both your time. Adding the property allows creating the table of activities and adding activities to it and determine the critical path. I wanted to end this topic with a piece of working code for others that have a similar need but I'm now facing the problem of how to extract data from the object returned by the routine for determining the critical path. This is another matter for me to find out, maybe asking for help in a separate thread.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.