Solved 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);
-
@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.htmlFor eg. the error can be solved by calling the JS functions in
Component.onCompleted
handlerComponent.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(); } }
-
Component.onCompleted: { var table = new CPM.ActivityList(); }
The scope of
table
is limited to that function.
You can instead create this ononClicked
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. -
@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.