BLOG
|
1. Introduction This blog is about the Javascript "for of" loop and the Javascript destructuring assignment. These are part of ECMAScript 6 features that were included in the ECMAScript 7 update of AppStudio 3.3 and Qt 5.12.1. We explore how these features may help with your AppStudio apps. 2. The "for of" loop Traditionally, to loop through an array we need to use an index variable (e.g. i): var census = [
{ country: "France", population: 66.99 },
{ country: "Australia", population: 24.6 },
{ country: "United States", population: 327.2 }
]
for (var i = 0; i < census.length; i++) {
var item = census[i]
console.log( item.country, item.population )
} In ECMAScript 6, we can use "for of" loop to make this easier: let census = [
{ country: "France", population: 66.99 },
{ country: "Australia", population: 24.6 },
{ country: "United States", population: 327.2 }
]
for ( let item of census ) {
console.log( item.country, item.population )
} The "for of" loop is new in ECMAScript 6 and created specifically for looping through arrays and, in general, Javascript iterators. 3. Unpacking object values Since we know each item have country and population fields, we can use the new ECMAScript destructuring assignment operator to extract it: let census = [
{ country: "France", population: 66.99 },
{ country: "Australia", population: 24.6 },
{ country: "United States", population: 327.2 }
]
for ( let item of census ) {
let { country, population } = item
console.log( country, population )
} We can even mix destructuring assignment with "for of": let census = [
{ country: "France", population: 66.99 },
{ country: "Australia", population: 24.6 },
{ country: "United States", population: 327.2 }
]
for ( let { country, population } of census ) {
console.log( country, population )
} You can get creative by combining this with arrow functions: let census = [
{ country: "France", population: 66.99 },
{ country: "Australia", population: 24.6 },
{ country: "United States", population: 327.2 }
]
let orderByPopulationDesc = (a, b) => -(a.population - b.population)
for ( let { country, population } of census.sort( orderByPopulationDesc ) ) {
console.log( country, population )
} 4. Unpacking arrays values Similarly, you can unpack arrays using the following destructuring assignment syntax: let [ year, month, day ] = [ "2019", "11", "07" ]
console.log(year) // 2019
console.log(month) // 11
console.log(day) // 07 5. Unpacking regular expression capture groups When you use "str.match" with regular expression you get an array of strings. You get additional strings in the array for each capture group. You need to process the array to extract each capture group, e.g. let str = "2019-11-07"
let m = str.match( /(\d{4})-(\d{2})-(\d{2})/ )
console.log( m.length ) // 4
console.log( m[0] ) // 2019-11-07
console.log( m[1] ) // 2019
console.log( m[2] ) // 11
console.log( m[3] ) // 07 The Javascript destructuring assignment here helps eliminate the need for having an intermediate array variable. The code becomes much shorter and clearer: let str = "2019-11-07"
let [ , year, month, day ] = str.match(/(\d{4})-(\d{2})-(\d{2})/)
console.log( year ) // 2019
console.log( month ) // 11
console.log( day ) // 07 6. Using destructuring assignment with SQL In the following example, we see that the destructuring assignment is useful in unpacking results from SQL queries: db.open()
db.query( "CREATE TABLE IF NOT EXISTS countries (country TEXT, population NUMBER)" )
db.query( "INSERT INTO countries VALUES ( 'France', 66.99 )" )
db.query( "INSERT INTO countries VALUES ( 'Australia', 24.6 )" )
db.query( "INSERT INTO countries VALUES ( 'USA', 327.2 )" )
let q = db.query( "SELECT * FROM countries" )
for ( let ok = q.first() ; ok ; ok = q.next() ) {
let { country, population } = q.values
console.log( country, population )
}
q.finish()
db.close() We can use "for of" syntax here if we make use of Javascript iterator and Javascript generator syntax: function* queryIterator(q) {
for ( let ok = q.first() ; ok ; ok = q.next() )
yield q.values
}
db.open()
db.query( "CREATE TABLE IF NOT EXISTS countries (country TEXT, population NUMBER)" )
db.query( "INSERT INTO countries VALUES ( 'France', 66.99 )" )
db.query( "INSERT INTO countries VALUES ( 'Australia', 24.6 )" )
db.query( "INSERT INTO countries VALUES ( 'USA', 327.2 )" )
let q = db.query( "SELECT * FROM countries" )
for ( let { country, population } of queryIterator( q ) ) {
console.log( country, population )
}
q.finish()
db.close() We will talk more about Javascript iterators and Javascript generators in a future blog. 7. Conclusion This blog introduces Javascript "for of" loop and Javascript destructuring assignments. These are some of the new Javascript ECMAScript 7 syntax improvements in AppStudio 3.3 and Qt 5.12.1 that can help you improve your code readability, clarity by helping you write shorter, easier to maintain code. These syntax improvements complement other Javascript improvements, such as Javascript iterators, Javascript generators and Javascript promises. We will introduce Javascript iterators and Javascript generators in a future blog. We will revisit Javascript promises and how they fit with all these Javascript syntax improvements.
... View more
07-17-2019
01:00 AM
|
0
|
0
|
1529
|
POST
|
Cookie management should be automatic. If the login attempt was done via a NetworkRequest, then, it would set the cookies for all subsequent NetworkRequest. So, any session cookies should be working automatically. Whilst one shouldn't need to, you can read the cookie header given to you from a server via the responseHeaders and feed them into subsequent NetworkRequest headers.
... View more
07-15-2019
09:03 PM
|
2
|
1
|
1322
|
POST
|
It really depends on your SVG. If there is an alpha channel defined (e.g. transparency), then, it may be sufficient to drop the SVG on top of something as plain as a colored Rectangle. If it's more sophisticated, then, I'd suggest you checkout ColorOverlay. https://doc.qt.io/qt-5/qml-qtgraphicaleffects-coloroverlay.html
... View more
07-03-2019
03:26 PM
|
2
|
1
|
5626
|
BLOG
|
Introduction If you're a parent to daughters you may know what I mean when I talk about obsessions with Unicorns. When we walk pass any book or toy featuring unicorns the our girls' eyes light up with that mandatory question, "can we buy that?". My response is "No!". Please, no more, no, N-O, No! This blog is really about TextInput validators: InputMask IntValidator DoubleValidator RegExpValidator InputValidator (in this example, we will show you how to say "No" to unicorns!) The first 4 is from Qt's runtime. The last is from AppStudio's AppFramework. InputMask TextField {
inputMask: "999-AAA"
placeholderText: qsTr("Type in a license plate of the pattern 999-AAA")
text: "123-XYZ"
color: acceptableInput ? "green" : "red"
selectByMouse: true
} View full InputMaskTest.qml on GitHub Gist. InputMask is a property on TextInput components such as TextField. It allows us to quickly define acceptable text input. In the above example we allow 6 character license plates that begin with 3 digits, followed by a dash and finishing with 3 characters. When acceptable input is entered the text field changes from "red" to "green" and the user can submit the input. The InputMask has the following features: When the field is "empty" you'll see placeholders for symbols like hyphen ('-'), you will not see the placeholder text When you type, you do not need to type the fixed symbols, e.g. hyphen ('-') The code is very short https://doc.qt.io/qt-5/qml-qtquick-textinput.html#inputMask-prop https://doc.qt.io/qt-5/qlineedit.html#inputMask-prop IntValidator TextField {
validator: IntValidator {
bottom: 25
top: 75
}
placeholderText: qsTr("Type in an integer between 25 and 75")
text: "34"
color: acceptableInput ? "green" : "red"
selectByMouse: true
} View full IntValidatorTest.qml on GitHub Gist. IntValidator is a QML component that can be assigned as a TextInput validator. It allows us to quickly define acceptable numerical input. In above example we allow any integer between 25 and 75. When acceptable input is entered the text field changes from "red" to "green" and the user can submit the input. The IntValidator has the following features: When the field is empty you can see the placeholder text You cannot type more than the number of characters hinted by the IntValidator (e.g. in the above example you cannot type more than two digits) https://doc.qt.io/qt-5/qml-qtquick-intvalidator.html DoubleValidator TextField {
validator: DoubleValidator {
bottom: 25.0
top: 75.0
}
placeholderText: qsTr("Type in a number between 25.0 and 75.0")
text: "34.5"
color: acceptableInput ? "green" : "red"
selectByMouse: true
} View full DoubleValidatorTest.qml on GitHub Gist. DoubleValidator is a QML component that can be assigned as a TextInput validator. It allows us to quickly define acceptable numerical input. In above example we allow any number between 25.0 and 75.0. When acceptable input is entered the text field changes from "red" to "green" and the user can submit the input. The DoubleValidator has the following features: When the field is empty you'll see the placeholder text You can type in any length (because decimal points are allowed) https://doc.qt.io/qt-5/qml-qtquick-doublevalidator.html RegExpValidator TextField {
validator: RegExpValidator {
regExp: /[0-9]{3}-[A-Za-z]{3}/
}
placeholderText: qsTr("Type in a license plate of the pattern 999-AAA")
text: "123-XYZ"
color: acceptableInput ? "green" : "red"
selectByMouse: true
} View full RegExpValidatorTest.qml on GitHub Gist. RegExpValidator is a QML component that can be assigned as a TextInput validator. It allows us to quickly define acceptable input. In the above example, we allow for license plates that begin with a 3 digit number, followed by a dash and ending with 3 letters. RegExpValidator has the following features: When the field is empty you'll see the placeholder text (different than InputMask) You must type the hyphen, it won't be automatically typed for you (different than InputMask) The regular expression is significantly more complex to implement and maintain https://doc.qt.io/qt-5/qml-qtquick-regexpvalidator.html InputValidator TextField {
validator: InputValidator {
validate: function (input, position) {
if (input.match(/unicorn/)) {
return InputValidator.Invalid
}
while (input.match(/canine/)) {
input = input.replace(/canine/, 'dog')
position -= 6
position += 3
}
let state = InputValidator.Intermediate
if (input.endsWith('.')) {
state = InputValidator.Acceptable
}
return { input, position, state }
}
}
placeholderText: qsTr("Type in a sentence ending with a fullstop. Avoid using 'unicorn' and 'canine'!")
text: "A dog is man's best friend."
selectByMouse: true
} View full InputValidatorTest.qml on GitHub Gist. InputValidator is a QML component that can be assigned as a TextInput validator. It allows us to supply a Javascript function to be the validator. The Javascript function receives both the original string and position as input. It can return an input state (InputValidator.Invalid, InputValidator.Intermediate or InputValidator.Acceptable). It can also return an object that contains an updated version of the string, position and state. Returning an updated string allows us to implement auto correction. In the above example we forbid the user from typing in "unicorn". It's not allowed! We also forbid the user from typing "canine". If they attempt to type "canine" it gets auto corrected as "dog". The sentence will never be full accepted until the user finishes it with a full stop ".". InputValidator has the following features: When the field is empty you'll see the placeholder text You can prohibit certain input (e.g. we can come up with a word blacklist, e.g. censor offensive words) You can correct / fix input (i.e. implement auto correct) You have full control over the experience Takes more effort to implement your rules https://doc.arcgis.com/en/appstudio/api/reference/framework/qml-arcgis-appframework-inputvalidator/ References https://doc.qt.io/qt-5/qml-qtquick-textinput.html#inputMask-prop https://doc.qt.io/qt-5/qlineedit.html#inputMask-prop https://doc.qt.io/qt-5/qml-qtquick-intvalidator.html https://doc.qt.io/qt-5/qml-qtquick-doublevalidator.html https://doc.qt.io/qt-5/qml-qtquick-regexpvalidator.html https://doc.arcgis.com/en/appstudio/api/reference/framework/qml-arcgis-appframework-inputvalidator/ Send us your feedback to appstudiofeedback@esri.com Become an AppStudio for ArcGIS developer! Watch this video on how to sign up for a free trial. Follow us on Twitter @AppStudioArcGIS to keep up-to-date on the latest information and let us know about your creations built using AppStudio to be featured in the AppStudio Showcase. The AppStudio team periodically hosts workshops and webinars; please click on this link to leave your email if you are interested in information regarding AppStudio events.
... View more
07-03-2019
01:00 AM
|
6
|
0
|
5057
|
BLOG
|
Hi everyone, I updated this blog to reflect that Babel compiles the async/await syntax into Generator functions which AppStudio 3.3, Qt 5.12.1 can run. I believe this blows Promise chaining out of the water.
... View more
06-24-2019
07:49 PM
|
0
|
0
|
869
|
BLOG
|
Hi Paul, Thanks for your feedback. Survey123 does have a quicksort implementation written in Javascript. I ran tests on both algorithms against 2000 records. Whilst differences were observed, they were considered negligible because we were comparing subsecond speeds. I then increased the sample data to 12960 records. The listModelSort took 7.3s. Survey123's SortListModel.qml took 19.3s. That is noticeable difference. It supports my view that native Array.sort() is better than coding your own qsort(). However, there's more to the story. listModelSort() spends only 1.3s performing the Array.sort() ! The weakness lies in the heavy handed approach to apply the indexes back to the ListModel which took the remainder 6.0s. Also, in the 12960 records scenario, all records were unsorted. That skews the results in favour of listModelSort(). Significant time is spent in Javascript in both algorithms. So, the qsort() and Array.sort() is compared on equal footing. In practice, the use case for SortedListModel is to call it often. So, if you're keeping your list sorted regularly, then, the incremental sort in both algorithms will become negligible then the heavy handed reordering that occurs in listModelSort() can become a weakness. Another difference is the SortedListModel is configured to work with property binding, i.e. the sortProperty which is different than supplying a compareFunction. This means if you are already invested in one implementation, it would take some degree of effort for you to transition to the other. Similarly, in the case of Survey123, we could not readily transition from SortedListModel.sort() to listModelSort() unless we sure we captured the property binding cases correctly. I leave it up to you to decide which "horse" to use for which "course" in your own AppStudio apps. Stephen
... View more
06-19-2019
05:33 PM
|
1
|
0
|
6622
|
BLOG
|
1. Introduction This blog post discusses sorting the QML ListModel. It's one of my favourite problems to solve. The blog also talks about a Sorting Cities sample app. That sample app implements and demonstrates the QML ListModel sort. The rest of the blog explains how the function works. 2. Sorting Cities sample app The Sorting Cities sample app is an AppStudio 3.3 app. It comes with five U.S. cities. The user can sort them by city name or by distance to ESRI. The sort order can be either ascending or descending. Item {
ColumnLayout {
anchors.fill: parent
anchors.margins: 10 * AppFramework.displayScaleFactor
spacing: 10 * AppFramework.displayScaleFactor
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
model: ListModel {
id: listModel
Component.onCompleted: {
add( "Hawaii", 21.289373, -157.917480 )
add( "Los Angeles", 34.052235, -118.243683 )
add( "New York City", 40.730610, -73.935242 )
add( "Redlands", 34.0555700, -117.1825400 )
add( "Seattle", 47.608013, -122.335167 )
}
function add(city, lon, lat) {
const esri = QtPositioning.coordinate( 34.0572522, -117.1945814 )
const coord = QtPositioning.coordinate( lon, lat )
const distance = coord.distanceTo( esri )
append( { city, coord, distance } )
}
}
delegate: ItemDelegate {
text: `${city} ${(distance / 1000).toFixed(2)} km`
}
}
Button {
text: qsTr(" City ")
onClicked: listModelSort( listModel,
(a, b) => a.city.localeCompare(b.city) )
}
Button {
text: qsTr(" City (Desc) ")
onClicked: listModelSort( listModel,
(a, b) => - a.city.localeCompare(b.city) )
}
Button {
text: qsTr(" Distance to Esri ")
onClicked: listModelSort( listModel,
(a, b) => (a.distance - b.distance) )
}
Button {
text: qsTr(" Distance to Esri (Desc) ")
onClicked: listModelSort( listModel,
(a, b) => - (a.distance - b.distance) )
}
}
function listModelSort(listModel, compareFunction) {
let indexes = [ ...Array(listModel.count).keys() ]
indexes.sort( (a, b) => compareFunction( listModel.get(a), listModel.get(b) ) )
let sorted = 0
while ( sorted < indexes.length && sorted === indexes[sorted] ) sorted++
if ( sorted === indexes.length ) return
for ( let i = sorted; i < indexes.length; i++ ) {
listModel.move( indexes[i], listModel.count - 1, 1 )
listModel.insert( indexes[i], { } )
}
listModel.remove( sorted, indexes.length - sorted )
}
} View the full SortingCities.qml on Github Gist. 3. ListModel vs Array If you compare the ListModel QML Type with the Javascript Array type, you'll find that Array has better methods. In particular, Array has a sort method whilst ListModel does not. When you assign an Array to a QML visual component, e.g. ListView, the Array behaves like a scalar value. If you were to push new records onto the Array the ListView will not update. There is no property binding between pushes to the Array and the ListView. To refresh the ListView you need to reassign the Array every time the Array changes. This will cause the ListView to repaint. This could lead to bad user experience. For instance, I may be writing an application that collates results from an online search. The results may arrive through a series of NetworkRequests to REST API end points. Each update will cause the ListView to repaint and scroll back to the top. This will be particularly annoying to the user if the user was already reading and scrolling through the results. The experience is different with ListModels. When you assign a ListModel to a ListView you don't need to assign it again. When you append new records to the ListModel, the ListView will receive sufficient change notification and will automatically update to reflect that new records have arrived. If the ListModel were to change whilst the user was reading and scrolling through the ListView, the current scrolled position and the selected item will be unchanged. The user would not experience any negative user experience with the ListView. In short, for good user experience, we prefer ListModels over Arrays. 4. Compare Functions as Arrow Functions Array sort (and QML ListModel sort) requires a compare function. A compare function takes any two elements from the Array (or ListModel) and returns an integer. It can be negative, zero or positive. For example, if one wanted to compare any two numbers, one could write the following compare function: function compareNumbers( a, b ) {
return a - b
} If a was greater than b then the function would return a positive number. If a was the same as b then the function would return zero. If a was less than b then the function would return a negative number. Then, you would use the compare function as follows: function compareNumbers( a, b ) {
return a - b
}
let numbers = [ 5, 2, 11, 3, 7 ]
numbers.sort( compareNumbers )
console.log( numbers ) // [ 2, 3, 5, 7, 11 ] If your compare function is simple, you can inline it with your sort call, i.e. let numbers = [ 5, 2, 11, 3, 7 ]
numbers.sort( function compareNumbers( a, b ) { return a - b } )
console.log( numbers ) // [ 2, 3, 5, 7, 11 ] In AppStudio 3.3, Qt 5.12.1 you can use the ECMAScript 6 arrow function syntax, i.e. let numbers = [ 5, 2, 11, 3, 7 ]
numbers.sort( (a,b) => a - b )
console.log( numbers ) // [ 2, 3, 5, 7, 11 ] You'll see the arrow function syntax allows us to omit the 'function' keyword, the function's name and the 'return' keyword. We make this choice if we believe such a choice improves our code readability and maintainability. In the Sorting Cities sample app, I used the arrow function syntax for all 4 buttons, e.g. Button {
text: qsTr(" City ")
onClicked: listModelSort( citiesListModel,
(a, b) => a.city.localeCompare(b.city) )
}
5. Implementing listModelSort There are plentiful sorting algorithms out there. Whilst we can implement one in QML / Javascript, I don't think this is a good idea. We would be implementing a sort algorithm that we would required to maintain. One of the best algorithms out there, Quicksort, is complex to transcribe correctly. Also such an implementation would be executing entirely in Javascript and will not be leveraging much from native code. Instead, I wanted to leverage from Array sort's implementation. In a nutshell, the algorithm would be: Copy items from the QML ListModel to an Array Perform an Array sort Copy the resultant items from the sorted Array back to the QML ListModel Turns out, we can optimized the above technique by not copying the items but linking to them via an index. i.e. The Array would be a set of integer indexes and we just be traversing it as a on the fly lookup into the QML ListModel. Let's begin by creating an indexes Array the exact same size as our ListModel with numbers ranging from 0 to N - 1: let indexes = [ ...Array(listModel.count).keys() ] Then, we use Array's sort method to reorder the indexes Array. We use an arrow function to pick any two entries from the QML ListModel and feed them to the user supplied compare function: indexes.sort( (a, b) => compareFunction( listModel.get(a), listModel.get(b) ) ) This reorders the indexes Array. We can now use this sorted Array to traverse the ListModel in sorted order. Whilst we can stop here, I wanted to go further and use theses indexes to rearrange the QML ListModel. for ( let i = 0; i < indexes.length; i++ ) {
listModel.move( indexes[i], listModel.count - 1, 1 )
listModel.insert( indexes[i], { } )
}
listModel.remove( 0, indexes.length ) The above loop iterates through the items in the ListModel in sorted order. For each item, we move it to the end of the ListModel in sorted order. Once all the items have been moved, we delete the empty holes left behind by the move. It's a heavy handed approach. In my final version I made an optimization that reduces the amount moving and deletion. The animation above helps you visualize sorting the cities based on their distance to ESRI. We see it quickly identifies the sort order as Redlands, Los Angeles, Seattle, New York City and Hawaii. The records are tagged by an index 0...4. Next, the sorted records are moved, one by one, to the end, then the sorted records are moved back to the top deleting the temporary empty holes. 6. Closing Remarks For good user interfaces I recommend storing your content in ListModels instead of Arrays. This is so that updates to the content will not interrupt the user experience. Whenever you can, leverage from an existing implementation (e.g. Array sort) rather than implement your own. You minimize your coding effort and future maintenance efforts. The ECMAScript 6 arrow function syntax can help you express simple sorting compare functions. Otherwise, if they're complex, then, then you should use a named compare function. 7. References https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array https://doc.qt.io/qt-5/qml-qtqml-models-listmodel.html https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n https://stackoverflow.com/questions/29429710/how-to-auto-sort-qml-listelements-in-sections/54682674#54682674 https://en.wikipedia.org/wiki/Quicksort Send us your feedback to appstudiofeedback@esri.com Become an AppStudio for ArcGIS developer! Watch this video on how to sign up for a free trial. Follow us on Twitter @AppStudioArcGIS to keep up-to-date on the latest information and let us know about your creations built using AppStudio to be featured in the AppStudio Showcase. The AppStudio team periodically hosts workshops and webinars; please click on this link to leave your email if you are interested in information regarding AppStudio events.
... View more
06-19-2019
01:00 AM
|
1
|
2
|
11711
|
POST
|
Hi Keith, This got fixed improved in Qt Creator 4.9.0, see https://bugreports.qt.io/browse/QTCREATORBUG-20341 A version of the Qt Creator 4.9.0 is shipped with Qt 5.12.4 (which just got release recently). Read about it here Qt 5.12.4 Released with support for OpenSSL 1.1.1 - Qt Blog. Qt 5.12.4 is now available via the maintenance tool of the online installer. For new installations, please download latest online installer from Qt Account portal or from qt.io Download page. Note that this version of Qt is not fully compatible with Local Make as per https://doc.arcgis.com/en/appstudio/extend-apps/installdevtoolslocalmake.htm. These set of instructions are provided just so that you can get a version of Qt Creator 4.9.0 that supports the ECMAScript 262 features for your arrow functions. After installing it, you need to configure AppStudio to use Qt Creator 4.9.0 instead of the Qt Creator 4.5.0 shipped with AppStudio. On Windows: In a Command Promt run: %USERPROFILE%\Applications\ArcGIS\AppStudio\sdk\ideintegration\post-install.bat Edit %APPDATA%\Esri\AppStudio.ini changing the value for [Tool-QtCreator] command: [Tool-QtCreator]
command=C:\\Qt\\Qt5.12.4\\Tools\\QtCreator\\bin\\qtcreator.exe on macOS: In a Terminal window run: ~/Applications/ArcGIS/AppStudio/sdk/ideintegration/post-install-mac.sh Edit ~/.config/Esri/AppStudio.ini changing the value for [Tool-QtCreator] command: [Tool-QtCreator]
command=/Users/stephenquan/Qt5.12.4/Qt Creator.app on Linux: In a terminal window run: ~/Applications/ArcGIS/AppStudio/sdk/ideintegration/post-install-linux.sh Edit ~/.config/Esri/AppStudio.ini changing the value for [Tool-QtCreator] command: [Tool-QtCreator]
command=/home/stephenquan/Qt5.12.4/Tools/QtCreator/bin/qtcreator.sh Stephen
... View more
06-18-2019
03:36 PM
|
1
|
0
|
1537
|
BLOG
|
1. Javascript Promise AppStudio 3.3, Qt 5.12.1 and ECMAScript 7 (which includes ECMAScript 6) introduced native Javascript Promises. This blog is to give you a brief overview of what Promises are and how they matter to AppStudio and QML. This blog isn't a comprehensive guide to Promises. There already is a lot of content on the internet. I recommend you seek them out. Promises is a Javascript coding pattern for working with asynchronous tasks. AppStudio (and QML) has signals and slots for working with asynchronous tasks. If your applications are written and easy to maintain then you probably don't need to look into Promises right now. However, if your applications are complex with business logic spanning over a complex sequence of signals and slots then buying into Promises may be the key to reduce code complexity. Note that the ArcGIS.AppFramework.Promises module is being deprecated in favour of native Javascript Promises. We advise discontinuing using ArcGIS.AppFramework.Promises module and begin removing them from your existing AppStudio apps. 2. Promise Download Sample Let's look at an AppStudio app that simulates downloading content from the internet. When the Button is clicked, we simulate 3 concurrent downloads. The downloads will finish 0-1000ms after the Button's onClicked handler has finished. Each download's success/failure will be independent of each other. Button {
text: qsTr("Test Promise timeout")
onClicked: {
download( "GET", "https://community.esri.com/groups/appstudio" )
.then( (message) => { console.log( message ) } )
.catch( (error) => { console.log( error ) } )
download( "GET", "https://appstudio.arcgis.com" )
.then( (message) => { console.log( message ) } )
.catch( (error) => { console.log( error ) } )
download( "GET", "https://community.esri.com/groups/survey123" )
.then( (message) => { console.log( message ) } )
.catch( (error) => { console.log( error ) } )
}
}
function download( method, url ) {
return new Promise( (resolve, reject) => {
if (Math.random() < 0.30)
setTimeout( reject, Math.floor(1000 * Math.random()), `Download failure ${url}` )
else
setTimeout( resolve, Math.floor(1000 * Math.random()), `Download success ${url}` )
} )
} View the full PromiseTimeout.qml on GitHub Gist. Some key features of Promises: The task begins as soon as new Promise() { ... } is called. The task is defined as (resolve, reject) => { /* body */ } where either resolve(data) or reject(error) will be called when the task completes. Use then() method to supply your success handler. Use catch() method to supply your failure handler. You may be thinking "This looking confusing" and "Do I need this?". I ask you to consider this: the Button's onClick handler fully describes 3 download tasks and what to do when each of them finishes. It describes this all this in one place, in the one function. If you didn't use Promises, we would have to create numerous signals and slots and spread the implementation throughout the application. 3. Promise chaining Promise chaining is a coding pattern where you want to run your asynchronous tasks sequentially. You may need to do this if you wish to use the output of one task as an input to the next. When the Button is clicked, we want to start the download of 3 web pages, one after the after. If downloads are successful, you will see the web page's title. If any task fails, we do not continue downloading. (N.B. here, the download function is real. The download function returns the web page's title). Button {
text: qsTr( "Test Promise chaining" )
onClicked: {
download( "GET", "https://community.esri.com/groups/appstudio" )
.then( (data) => {
console.log( data.title )
return download( "GET", "https://appstudio.arcgis.com" )
} )
.then( (data) => {
console.log( data.title )
return download( "GET", "https://community.esri.com/groups/survey123" )
} )
.then( (data) => {
console.log( data.title )
} )
.catch( (error) => { console.log( error ) } )
}
} View the full PromiseChaining.qml on GitHub Gist. However, due to the https://bugreports.qt.io/browse/QTBUG-71329, Promise chaining doesn't work in AppStudio 3.3, Qt 5.12.1. Please vote to have it fixed! Because of the bug, the app does not run correctly: 4. Promise nesting (alternative to Promise chaining) Since Promise chaining doesn't work in AppStudio 3.3, Qt 5.12.1 let's explore some alternatives. This one demonstrates Promises nesting where each subsequent task is nested in the success of the previous task. (N.B. here the download function returns an object containing the status, responseText, and title). Button {
text: qsTr( "Test Promise nesting" )
onClicked: {
download( "GET", "https://community.esri.com/groups/appstudio" )
.then( (data) => {
console.log( data.title )
download( "GET", "https://appstudio.arcgis.com" )
.then( (data) => {
console.log( data.title )
download( "GET", "https://community.esri.com/groups/survey123" )
.then( (data) => {
console.log( data.title )
} )
.catch( (error) => { console.log( error ) } )
} )
.catch( (error) => { console.log( error ) } )
} )
.catch( (error) => { console.log( error) } )
}
} View the full PromiseNesting.qml on GitHub Gist. This implementation works, but it is extremely ugly. It is infamously known as callbackhell. You see each subsequent task incurs another level of indentation and another copy of the error handler. 5. Promise async/await (alternative to Promise chaining) async/await is another alternative to Promise chaining. The main characteristic is the use of the new async and await keywords and that the main body reads sequentially and elegantly. Button {
text: qsTr( "Test Promise async/await" )
onClicked: {
(async () => {
try {
console.log( ( await download( "GET", "https://community.esri.com/groups/appstudio") ).title)
console.log( ( await download( "GET", "https://appstudio.arcgis.com") ).title)
console.log( ( await download( "GET", "https://community.esri.com/groups/survey123" ) ).title)
} catch ( error ) {
console.log( error )
}
} )()
}
} View the full PromiseAsyncAwait.qml on GitHub Gist. Unfortunately, this feature didn't make it to AppStudio 3.3, Qt 5.12.1. Help get this into a future release of AppStudio by voting for it at https://bugreports.qt.io/browse/QTBUG-58620. 6. Promise async/await Babel (alternative to Promise chaining) Generator functions made it into AppStudio 3.3, Qt 5.12.1. We can use https://babeljs.io/ to convert the async/await into Generator functions. This version works in AppStudio 3.3, Qt 5.12.1. Here's the converted output: Button {
text: qsTr( "Test Promise async/await (Babel)" )
onClicked: {
_asyncToGenerator( function*() {
try {
console.log( ( yield download( "GET", "https://appstudio.arcgis.com" ) ).title )
console.log( ( yield download( "GET", "https://community.esri.com/groups/appstudio" ) ).title )
console.log( ( yield download( "GET", "https://community.esri.com/groups/survey123" ) ).title )
} catch ( error ) {
console.log( error )
}
} )()
}
} View the full PromiseBabel.qml on GitHub Gist. While waiting for the async/await feature to be implemented, I think the above syntax is compelling. It is extremely close to async/await syntax and, I think, is significantly better than Promise chaining. To make it work, _asyncToGenerator() and asyncGeneratorStep() functions are required. 7. Promise.all and Promise.race Promise.all is a special Promise that succeeds by waiting for every sub Promise to succeeds else it fails to report the error from the first failing sub Promise. Promise.race is a special Promise in that we are only interested in the first sub Promise that finishes the race. We aren't interested in the completion of the subsequent Promises. We will be notified of the success message or failure error of that first sub Promise. Surprisingly, no matter how many times you try, you find the AppStudio Home Page always wins the race. Why is the second Promise always succeeding in the race? That's because the community GeoNet page takes longer to load! 8. Closing remarks I hope this blog helps gives a good overview ofPromises and why they matter in your AppStudio apps. Promises, while new in AppStudio 3.3, Qt 5.12.1 is a feature that other Javascript developers have already been enjoying for some time now. Promises, I believe, work very well with Qt's signal and slot mechanism taking it to a whole new level. Promise chaining and async/await functions do not work. We need your help to make AppStudio come closer to parity with other Javascript developer environments by voting on getting these two bugs fixed: https://bugreports.qt.io/browse/QTBUG-71329 https://bugreports.qt.io/browse/QTBUG-58620 Whilst we wait for async/await functions, I highly recommend you use https://babeljs.io/ to get async/await features today. 9. References https://community.esri.com/groups/appstudio/blog/2019/05/22/ecmascript-7-settimeout-and-arrow-functions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await https://doc.arcgis.com/en/appstudio/api/reference/framework/qml-arcgis-appframework-networkrequest/ https://doc.qt.io/qt-5/qml-qtqml-timer.html https://doc.qt.io/qt-5/qml-qtqml-component.html https://bugreports.qt.io/browse/QTBUG-71329 https://bugreports.qt.io/browse/QTBUG-58620 Send us your feedback to appstudiofeedback@esri.com Become an AppStudio for ArcGIS developer! Watch this video on how to sign up for a free trial. Follow us on Twitter @AppStudioArcGIS to keep up-to-date on the latest information and let us know about your creations built using AppStudio to be featured in the AppStudio Showcase. The AppStudio team periodically hosts workshops and webinars; please click on this link to leave your email if you are interested in information regarding AppStudio events.
... View more
06-05-2019
01:00 AM
|
2
|
4
|
2829
|
BLOG
|
Glad that it helped! Thanks for the upvote on the bug.
... View more
06-01-2019
02:19 PM
|
0
|
0
|
17128
|
BLOG
|
1. Introduction This blog talks about implementing your own setTimeout() function in AppStudio. The blog also highlights ECMAScript 6 and ECMAScript 7 support in Qt 5.12.1 and AppStudio 3.3 features: arrow functions (my favourite), property shorthand, rest parameters and spread syntax. setTimeout() is a function which executes another function or specified piece of code after waiting for the specified time delay in milliseconds. The following will execute the following 4 console.log() after waiting 0 - 1000 milliseconds (determined by a random function). Each timer starts simulatenously but their completion times are random making the 4 console.log() appearing in random jumbled order. setTimeout( () => console.log("Hello"), randomDelay() )
setTimeout( (a) => console.log(a), randomDelay(), 1 )
setTimeout( (a, b) => console.log(a, b), randomDelay(), 21, 22 )
setTimeout( (a, b, c) => console.log(a, b, c), randomDelay(), 31, 32, 33 ) randomDelay() is a function that computes a random time delay from the range of 0 – 1000 milliseconds: function randomDelay() {
return Math.floor(1000 * Math.random())
} Here is the console output: 2. AppStudio implementation setTimeout() is a part of other Javascript development environments, e.g. in Web development and Node.js. It exists globally on the window object. However, in Qt and AppStudio, there isn’t an equivalent window object or setTimeout() function. To bring over such a capability you need to implement it yourself. Here is a sample AppStudio app that includes a simple implementation of setTimeout(). Everytime the button is clicked the 4 setTimeout() functions are triggered with the 4 resulting console.log() appearing in random order. import QtQuick 2.12
import QtQuick.Controls 2.5
import ArcGIS.AppFramework 1.0
App {
id: app
width: 400 * AppFramework.displayScaleFactor
height: 640 * AppFramework.displayScaleFactor
StackView {
id: stackView
anchors.fill: parent
initialItem: Item {
Button {
text: qsTr("Test")
onClicked: {
setTimeout( () => console.log("Hello"), randomDelay() )
setTimeout( (a) => console.log(a), randomDelay(), 1 )
setTimeout( (a, b) => console.log(a, b), randomDelay(), 21, 22 )
setTimeout( (a, b, c) => console.log(a, b, c), randomDelay(), 31, 32, 33 )
}
}
}
}
function randomDelay() {
return Math.floor(1000 * Math.random())
}
function setTimeout(func, interval, ...params) {
return setTimeoutComponent.createObject(app, { func, interval, params } )
}
function clearTimeout(timerObj) {
timerObj.stop()
timerObj.destroy()
}
Component {
id: setTimeoutComponent
Timer {
property var func
property var params
running: true
repeat: false
onTriggered: {
func(...params)
destroy()
}
}
}
}
Disclaimer: this is a minimal implementation of setTimeout() not a fully functioning implementation. It is here for the purposes of this blog, specifically to talk about aspects of ECMAScript. If you want to use this implementation you will need to adapt it accordingly. This implementation of setTimeout() returns a Timer object (instead of a timerId). When the time delay specified in milliseconds has been reached the nominated Javascript function is triggered with the parameters you supplied to setTimeout(). If you wish to abort the action, you can passed the Timer object to the clearTimeout() function. This implementation will now be deconstructed: Global functions in AppStudio ECMAScript 6 Arrow Functions ECMAScript 6 Property shorthand ECMAScript 6 Rest parameter and Spread syntax 3. Global functions in AppStudio Functions implemented in the top level component (here, it was App) will be seen by all child components. In this example, we have a StackView which means, all pages that are pushed to the StackView will have access to the setTimeout() function. 4. Arrow functions The first parameter to setTimeout() refers to a function or code. In our examples, we have been using the ECMAScript 6 arrow function syntax for anonymous functions. e.g. the arrow function (a, b, c) => console.log(a, b, c) appears in the following snippet: setTimeout( (a, b, c) => console.log(a, b, c), randomDelay(), 31, 32, 33) When used appropriately, arrow functions can help improve code readablity and maintainability. For comparison, let's look at equivalent implementation to the above arrow function. 4.1 Original Anonymous functions Prior to ECMAScript 6, we could always do anonymous functions, but the syntax was somewhat cumbersome with the required use of the function keyword: setTimeout( function (a, b, c) { console.log(a, b, c) }, randomDelay(), 31, 32, 33 ) 4.2 Using a named function Sometimes, to avoid the use of the anonymous function syntax, I find it better to name things clearly and space out my code for increase readability and maintainability: function testFunction(a, b, c) {
console.log(a, b, c)
}
setTimeout(testFunction, randomDelay(), 31, 32, 33) 4.3 Calling console.log directly For completeness, we recognize that console.log, is, itself, a function reference, so, we can supply this directly to the setTimeout() function. setTimeout(console.log, randomDelay(), 31, 32, 33) 5. Property shorthand syntax ECMAScript 6 introduces a property shorthand syntax for object creation. In the following snippets, compare the code for both obj1 and obj2. They both yield the same value, but the code for obj2 is much shorter and easier to read. var x = 11
var y = 22
var z = 33
var obj1 = { x: x, y: y, z: z } // obj1 = { x: 11, y: 22, z: 33 }
var obj2 = { x, y, z } // obj2 = { x: 11, y: 22, z: 33 } We used the shorthand syntax to simplify the following setTimeoutComponent.createObject(app, { func:func, internval:interval, args:args } ) into: setTimeoutComponent.createObject(app, { func, interval, args } ) 6. Rest parameter and Spread syntax ECMAScript 6 also introduces rest parameters and spread syntax. We used it in the declaration of the setTimeout function to pick up all remaining parameters as an array. function setTimeout(func, interval, ...params) { when called with: setTimeout( (a, b, c) => console.log(a, b, c), randomDelay(), 31, 32, 33) results in: func = (a, b, c) => console.log(a, b, c)
interval = randomDelay()
params = [ 31, 32, 33 ] When calling func we passed the parameters using the spread syntax, i.e. func(...params) which passes the parameter array as individual parameters to the function, i.e. func(31, 32, 33) which ultimate calls console.log(31, 32, 33) Closing Remarks This blog discusses an AppStudio implementation of setTimeout(). It was used as a way to give you a glimpse of some of the new ECMAScript 6 features, i.e. arrow functions, property shorthand and rest parameters and spread syntax which you can utilize in your AppStudio apps. I plan to talk about Promises in ECMAScript 6 and AppStudio in a future blog. That blog will reuse the setTimeout() function and arrow functions introduced here. In the meantime, please help us improve Promises in AppStudio by voting on fixing Promise chains bug https://bugreports.qt.io/browse/QTBUG-71329 and by voting on implementing async/await support in https://bugreports.qt.io/browse/QTBUG-58620 References https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions http://es6-features.org/#ExpressionBodies https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer https://es6-features.org/#PropertyShorthand https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise https://bugreports.qt.io/browse/QTBUG-71329 Send us your feedback to appstudiofeedback@esri.com Become an AppStudio for ArcGIS developer! Watch this video on how to sign up for a free trial. Follow us on Twitter @AppStudioArcGIS to keep up-to-date on the latest information and let us know about your creations built using AppStudio to be featured in the AppStudio Showcase. The AppStudio team periodically hosts workshops and webinars; please click on this link to leave your email if you are interested in information regarding AppStudio events.
... View more
05-22-2019
01:00 AM
|
2
|
2
|
35653
|
BLOG
|
1. Introduction This amazing clipart can be produced with a surprising short amount of code: Text {
text: "\u{1f304}"
font.pointSize: 128
} View EmojiSunrise.qml on GitHub Gist. This blog talks about Unicode and the wonderful emojis and cliparts that are hidden within. It also covers some new syntax introduced in AppStudio 3.3, Qt 5.12.1 and ECMAScript 7 (which includes ECMAScript 6). 2. Use Unicode Clipart to rapidly create UI You can use Unicode emojis and clipart in Buttons and MenuItems: import QtQuick 2.7
import QtQuick.Controls 2.2
import ArcGIS.AppFramework 1.0
App {
id: app
width: 400 * AppFramework.displayScaleFactor
height: 640 * AppFramework.displayScaleFactor
Button {
id: menuButton
anchors.right: parent.right
anchors.margins: 10
text: "\u{2630}"
onClicked: menu.open()
}
Menu {
id: menu
x: menuButton.x + menuButton.width - menu.width
y: menuButton.y + menuButton.height
MenuItem { text: "\u{1f4c2} Open" }
MenuItem { text: "\u{1f4be} Save" }
MenuItem { text: "\u{1f5a8} Print" }
MenuItem { text: "\u{2702} Cut" }
MenuItem { text: "\u{1f4cb} Paste" }
MenuItem { text: "\u{1f4c5} Calendar" }
MenuItem { text: "\u{2699} Settings" }
MenuItem { text: "\u{1f6aa} Exit" }
}
} You can use emojis to improve text shown to the user: import QtQuick 2.7
import ArcGIS.AppFramework 1.0
App {
id: app
width: 520 * AppFramework.displayScaleFactor
height: 640 * AppFramework.displayScaleFactor
ListView {
anchors.fill: parent
model: [
' \u{2139} "Do. Or do not. There is no try." — Yoda',
' \u{26a0} "It’s not wise to upset a Wookiee." - Han Solo',
' \u{1f534} "It’s a trap!" - Admiral Ackbar'
]
delegate: Text { text: modelData }
}
} 3. Where is the list of emojis? Using the String.fromCodePoint(codePoint) you can display any Unicode characters base on its code point number. You can use this code pattern to iterate code points searching for interesting emojis and clipart. The following shows the emojis and clipart from 1F300 to 1F3FF. import QtQuick 2.7
import ArcGIS.AppFramework 1.0
App {
id: app
width: 520 * AppFramework.displayScaleFactor
height: 640 * AppFramework.displayScaleFactor
property int startCodePoint: 0x1f300
property int endCodePoint: 0x1f3ff
property int codePoint: startCodePoint
Text {
anchors.centerIn: parent
text: String.fromCodePoint(codePoint)
font.pointSize: 64
}
Timer {
id: animTimer
repeat: true
running: true
interval: 100
onTriggered: codePoint = (codePoint < endCodePoint) ? codePoint + 1 : startCodePoint
}
} You can also use online lists to search for Unicode emojis and clipart, i.e. "sunrise over mountains" can be found in the following lists: https://unicode.org/emoji/charts/full-emoji-list.html#1f304 https://emojipedia.org/sunrise-over-mountains/ Some interesting Unicode blocks are 2600 - 2BFF and 1F300 - 1F8FF. For a listing of Unicode blocks refer to: https://en.wikipedia.org/wiki/Unicode_block 4. Character vs Code Point A character is represented internally as a 16 bit number which means there can be a total of 65536. Unicode allows for 17 planes, each of 65,536 characters. This gives a total of 1,114,112 possible characters (i.e. 0x to 0x10ffff). If a Unicode Code Point is within a 16 bit number, it will only require 1 character to represent, otherwise, we will need two characters. For instance, in AppStudio 3.2, Qt 5.11.2 and ES 5, we needed two characters to represent the sunrise over mountains emoji, i.e. "\ud83c\udf04". The code point syntax "\u{1f304}" in AppStudio 3.3, Qt 5.12.1 and ECMAScript 7 (which includes ECMAScript 6) is new. It actually expands into the two characters that make it. The String.fromCodePoint() function is also new. "\u{1f304}" === String.fromCodePoint(0x1f304) we can always convert code point back into their numbers with: "\u{1f304}".codePointAt(0) === 0x1f304 We can also convert code points to characters: "\u{1f304}".length === 2
"\u{1f304}".charCodeAt(0) === 0xd83c
"\u{1f304}".charCodeAt(1) === 0xdf04
"\u{1f304}" === "\u{d83c}\u{df04}" 5. Caveats 5.1 Not all clipart is available or consistent cross platform When viewing Unicode clipart on Windows 10 you find a set of Mahjong tiles at 1F000 - 1F02B. You might be inclined to use them in a Mahjong app: However, when you check on other platforms, you may quickly change your mind. On Linux, we get the complete set of clipart, but, in black and white. On macOS, all of the characters appear black and white except for Mahjong Red Dragon appears in colour. On iOS, only the Mahjong Red Dragon appears whereas all other characters are unavailable. On Android, only the Mahjong Red Dragon tile is available there too. Also note the appearance of the no data tile is different on every platform. On Linux: On macOS: On iOS: On Android: 5.2 Issue with some CodePoints on Buttons In the following code snippet we try to render the same text in Text and Button components: import QtQuick 2.7
import QtQuick.Controls 2.2
import ArcGIS.AppFramework 1.0
App {
id: app
width: 520 * AppFramework.displayScaleFactor
height: 640 * AppFramework.displayScaleFactor
Column {
Text {
text: "\u{263a} \u{1f304} "
font.pointSize: 32
}
Button {
text: "\u{263a} \u{1f304} "
font.pointSize: 32
font.capitalization: Font.MixedCase
}
}
} we see that the smiley and the face emoji renders okay on both the Text and the Button However, the Button requires a workaround that disables the material design capitalization on Buttons. This is due to a bug in Qt 5.12.x reported in https://bugreports.qt.io/browse/QTBUG-75559. Help get this bug fix by voting on it. Summary You can find a lot of clipart or emojis that help deliver high visual impact for little expense in code. Caveats are not every platform offers the same set of clipart or emojis nor are they consistent in appearance on the ones that they do offer. Cross platform testing is essential. References: https://stackoverflow.com/questions/5924105/how-many-characters-can-be-mapped-with-unicode https://en.wikipedia.org/wiki/Unicode https://en.wikipedia.org/wiki/Code_point https://unicode.org/emoji/charts/full-emoji-list.html https://www.fileformat.info/info/unicode/char/1f304/index.htm https://emojipedia.org/sunrise-over-mountains/ https://unicode.org/emoji/charts/full-emoji-list.html#1f304 https://exploringjs.com/es6/ch_unicode.html https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt https://en.wikipedia.org/wiki/Mahjong_Tiles_(Unicode_block) https://en.wikipedia.org/wiki/Unicode_block Send us your feedback to appstudiofeedback@esri.com Become an AppStudio for ArcGIS developer! Watch this video on how to sign up for a free trial. Follow us on Twitter @AppStudioArcGIS to keep up-to-date on the latest information and let us know about your creations built using AppStudio to be featured in the AppStudio Showcase. The AppStudio team periodically hosts workshops and webinars; please click on this link to leave your email if you are interested in information regarding AppStudio events.
... View more
05-08-2019
01:00 AM
|
2
|
0
|
1878
|
BLOG
|
1. Mary had a little lamb AppStudio 3.3 is based on Qt5.12.1 which includes a new JavaScript engine supporting ECMAScript 6 and ECMAScript 7. In this blog we will be looking at multiline string literals, specifically, different ways of realizing the following nursery rhyme: Mary had a little lamb, little lamb, little lamb
Mary had a little lamb
Whose fleece was white as snow.
2. The 1 liner example var str = "Mary had a little lamb, little lamb, little lamb\nMary had a little lamb\nWhose fleece was white as snow.\n" The above code gives us what we want. A 3 line rhyme stored in a string. It's short, but, it is hard to read and maintain. The newline \n character makes reading the first word in the following sentence awkward. 3. String lists var list = [ "Mary had a little lamb, little lamb, little lamb",
"Mary had a little lamb",
"Whose fleece was white as snow." ]
var str = list.join("\n") + "\n" Using a staging list means we don't need to remember to put \n to break every line. It's looks nicer in code and easier to maintain the rhyme since we can now split the code over multiple lines. It does suffer from the fact that we need to ensure the syntax of lists is correct (i.e. the commas needed to be correctly placed). It has the advantage that we can make use of array functions such as appending to the end of the list. The joining line does look awkward but it's always the same pattern so you'll get used to it. Because we are using join, we must remember to append the last \n. 4. Using the files var str = app.folder.readTextFile("rhyme.txt") By storing strings in files, we can dramatically reduce the code that appears in your program. This reduces error significantly, and, maintenance on your rhyme can be done in a regular text edit without worrying about the Javascript side of things. 5. ECMAScript 6 multiline string var str = `Mary had a little lamb, little lamb, little lamb
Mary had a little lamb
Whose fleece was white as snow.
` As of AppStudio 3.3 (and Qt 5.12.x) we can now use backticks ` to surround a multiline string element. This is incredibly easy to read and maintain. Backticks also introduces a new feature called expression interpolation. Here's a simple example involving our poem: var name = 'Mary';
var str = `${name} had a little lamb, little lamb, little lamb
${name} had a little lamb
Whose fleece was white as snow.
` Here's a more extended form which includes operators: var customer = { name: "Foo" }
var card = { amount: 7, product: "Bar", unitprice: 42 }
var str = `Hello ${customer.name},
want to buy ${card.amount} ${card.product} for
a total of ${card.amount * card.unitprice} bucks?` References https://doc.qt.io/qt-5/whatsnew512.html https://www.ecma-international.org/ecma-262/7.0/ https://es6-features.org/ Send us your feedback to appstudiofeedback@esri.com Become an AppStudio for ArcGIS developer! Watch this video on how to sign up for a free trial. Follow us on Twitter @AppStudioArcGIS to keep up-to-date on the latest information and let us know about your creations built using AppStudio to be featured in the AppStudio Showcase. The AppStudio team periodically hosts workshops and webinars; please click on this link to leave your email if you are interested in information regarding AppStudio events.
... View more
04-24-2019
01:00 AM
|
2
|
0
|
1361
|
POST
|
Hi Jeremy, This may be fixed in AppStudio 3.3. Can you try the beta and give us feedback? Stephen
... View more
04-03-2019
05:34 PM
|
0
|
0
|
552
|
POST
|
Hi Christopher, There are a number of useful properties and methods on the AppFramework object, such as: AppFramework.userHomeFolder - AppFramework QML Type | ArcGIS AppFramework.fileFolder - AppFramework QML Type | ArcGIS AppFramework.fileInfo - AppFramework QML Type | ArcGIS Also, while we're at it, there is a useful method on the App object itself, app.folder - App QML Type | ArcGIS function checkExists(fileName) {
return app.folder.fileExists(fileName);
// app.folder will be a FileFolder object for your app, e.g. /var/mobile/Containers/Data/Application/F343D1DB-5FBC-488C-919A-6A6893944420/Documents
// then you just call fileExists('img_0050.jpg')
}
Stephen
... View more
03-28-2019
04:55 PM
|
0
|
0
|
405
|
Title | Kudos | Posted |
---|---|---|
1 | 03-13-2023 08:11 PM | |
2 | 02-21-2023 07:29 PM | |
1 | 06-19-2019 05:33 PM | |
1 | 06-19-2019 01:00 AM | |
1 | 06-01-2017 04:34 PM |
Online Status |
Offline
|
Date Last Visited |
09-18-2023
02:55 AM
|