Skip navigation
All Places > AppStudio for ArcGIS > Blog > 2019 > July
2019

1. Introduction

 

Many, many, many years ago, in my local bookstore, I had my first experiences with computers. I typed in my first program and always thought what it did was cool:

 

10 PRINT "HELLO"
20 GOTO 10

 

 

Today, I'm going to look into iterators and generators, and, yes, make a connection to how these latest features in ECMAScript 7 update of AppStudio 3.3 and Qt 5.12.1 aren't too dissimilar from old school concepts.

 

 

2. For Of Iterators

 

 

In my previous blog, I talked about "For Of" and showed that it works with Javascript iterators and Javascript generators.

 

function *cities() {
    yield { name: "Hawaii", distance: 4221.73 }
    yield { name: "Los Angeles", distance: 96.65 }
    yield { name: "New York City", distance: 3853.10 }
    yield { name: "Redlands", distance: 1.12 }
    yield { name: "Seattle", distance: 1566.70 }
}

for ( let city of cities() )
    console.log( JSON.stringify( city ) )

View the full ForIter.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

In the above example, we declare a Javascript generator which produces cities with their name and their distance from ESRI. We turn that Javascript generator into a Javascript iterator when we "call" it, i.e. cities(). As a Javascript iterator, we use a "for of" loop to process the results.

 

So far, nothing special. You can glean this sort of thing from any blog about Javascript iterators.

 

If we replace the "for of" syntax with a somewhat traditional looking "while" loop, we get to see some inner workings of the Javascript iterator:

 

function *cities() {
    yield { name: "Hawaii", distance: 4221.73 }
    yield { name: "Los Angeles", distance: 96.65 }
    yield { name: "New York City", distance: 3853.10 }
    yield { name: "Redlands", distance: 1.12 }
    yield { name: "Seattle", distance: 1566.70 }
}

const iter = cities()
let city = iter.next()
while ( !city.done ) {
    console.log( JSON.stringify( city.value ) )
    city = iter.next()
}

View the full WhileIter.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

The first impression is this "while" version looks a bit hard to swallow. The "for of" shorten version looks cleaner. When you think about it, this version actually gives you more insight into the "yield" keyword. When "yield" is used, a value is returned from the Javascript generator, and the Javascript generator pauses execution until the next time we call "iter.next()".

 

At first, this may seem like an alien concept, but, if you look harder, you'll see this pattern in a lot of places, some quite old.

 

3. Unix head and tails

 

On Unix, we learn that in shell programming, we can join Unix commands together using Unix pipes. A pipe is a form of redirection that is used in Unix to send output from one program to another for further processing. The first process is not completed before the second is started. They are executed concurrently.

 

$ cat cities.txt

 

 

$ cat cities.txt | head -3

 

 

$ cat cities.txt | head -3 | tail -2


 

Whilst cat can produce 5 lines of text, in the above pipeline, it doesn't. The 3 Unix commands, cat, head and tail are executing concurrently. The cat command only needs to generate output, if the subsequent head and tail commands require it. In this example, we only require cat to generate 3 lines of text to fullfill the requirements of the subsequent head command.

 

We can mirror the above Unix statement in QML.

 

Button {
    text: qsTr("LA and NYC cities")

    onClicked: {
        function *cities() {
            yield { name: "Hawaii", distance: 4221.73 }
            yield { name: "Los Angeles", distance: 96.65 }
            yield { name: "New York City", distance: 3853.10 }
            yield { name: "Redlands", distance: 1.12 }
            yield { name: "Seattle", distance: 1566.70 }
        }

        function* cat( iter ) {
            yield* iter
        }

        function* head( iter, n ) {
            if (n <= 0) return
            for (let item of iter) {
                yield item
                if (--n <= 0) break
            }
       }

       function* tail( iter, n ) {
           let arr = [ ]
           for ( let item of iter ) {
               arr.push(item)
               if (arr.length > n) arr.shift()
           }
           yield* arr
       }

       for ( let city of tail( head( cat( cities() ), 3), 2) )
           console.log( JSON.stringify( city ) )
    }
}

View the full HeadAndTails.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

Similar to the Unix head, tail, cat commands, the above head, tail, cat Javascript generator functions start executing concurrently. The data pipeline occurs when callers consume data and the Javascript generator functions yield data.

 

Because of the requirements of the head generator call, we only require the cat generator and the cities generator to generate 3 lines of output.

 

 

4. Infinite loops no longer taboo

 

 

Traditionally, we consider CPU intensive and/or infinite loops to be taboo in Javascript.

 

function helloWorldTaboo() {
    while (true)
        console.log( "Hello World" )
}

 

They block the UI, they make the program unresponsive, they may eventually lead to a crash. We consider such code taboo and we have been trained to not write in that style. We require the developer to rewrite their applications to use signals and slots or callback functions that help unblock the UI.

 

Rules changes when it comes to Javascript generators and Javascript iterators. Apparent infinite loops are no longer taboo:

 

function* helloWorldGenerator() {
    while (true)
        yield "Hello World"
}

 

The following demonstrates the Javascript generator working with a Timer object. The program is not CPU intensive and not quite an infinite loop in that one can stop the invoking the Javascript generator by stopping the Timer.

 

 

Button {
    text: qsTr("Run!")

    onClicked: {
        function* helloWorldGenerator() {
            while (true)
                yield "Hello World"
        }

        helloWorldTimer.iter = helloWorldGenerator()
        helloWorldTimer.start()
    }
}

Timer {
    id: helloWorldTimer
    property var iter
    running: false
    repeat: true
    interval: 100
    onTriggered: {
        let item = iter.next()
        if (item.done) {
            stop()
            return
        }
        console.log( item.value )
    }
}

View the full InfiniteHello.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

 

5. Generators are bidirectional

 

 

In Unix, the data flows in Unix pipes in one direction. With Javascript generators, the data flow can be bidirectional. We can take a value from the Javascript generator modify and send the result back to the Javascript generator.

 

The following shows the generator yielding 1, 2 and 3 respectively. For each value, we square the result and send the square back to the generator.

 

function* gen() {
  console.log( yield 1 )
  console.log( yield 2 )
  console.log( yield 3 )
}

let iter = gen()
let v = iter.next()
console.log( JSON.stringify(v) )
v = iter.next( v.value * v.value )
console.log( JSON.stringify(v) )
v = iter.next( v.value * v.value )
console.log( JSON.stringify(v) )
v = iter.next( v.value * v.value )
console.log( JSON.stringify(v) )

View the full Bidirectional.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

 

6. Mixing Javascript generators with Promises

 

 

In a previous blog on Promises, I talked about async / await missing in AppStudio 3.3 and Qt 5.12.1 and that you can vote to have it included it https://bugreports.qt.io/browse/QTBUG-58620.

 

I've read in other blogs that async / await is considered by some as syntactic sugar on Javascript generators yielding Promises.

 

I gave the following example demonstrating what this looks like with _asyncToGenerator() a function that works with Promises inside Javascript generators:

 

Button {
    text: qsTr( "Test Promise async/await (Babel)" )

    onClicked: {
        _asyncToGenerator( function*() {
            try {
                showTitle( yield download( "GET", "https://appstudio.arcgis.com" ) )
                showTitle( yield download( "GET", "https://community.esri.com/groups/appstudio" ) )
                showTitle( yield download( "GET",
"https://community.esri.com/groups/survey123" ) )
            } catch ( err ) {
                console.log( "Caught Error: ", err.message )
                throw err
            }
        } )()
    }
}

View the full PromiseBabel.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

 

6. Using ArcGIS Online Search API

 

The following uses Promises to invoke the ArcGIS Online Search API iteratively to retrieve titles of over 200+ AppStudio samples:

 

Button {
    text: qsTr("Search")

    onClicked: {
        _asyncToGenerator( function*() {
            networkRequest.url = "https://www.arcgis.com/sharing/rest/search"
            networkRequest.responseType = "json"
            try {
                let f = "pjson"
                let q = "type:Native Application owner:appstudio_samples"
                let start = 1
                let num = 10
                while (start !== -1 ) {
                    let resp = yield networkRequest.sendWithPromise( { f, q, start, num } )
                    let { nextStart, total, results } = resp.response
                    for ( let result of results )
                        console.log( result.title )
                    start = nextStart
                }
            } catch (err) {
                console.log("Caught exception: ", err.message)
                console.log(err.stack)
                throw err
            }
        } )()
    }
}

View the full ArcGISOnlineSearch.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

 

7. Maze Solving Algorithm

 

 

The following demonstrates how you can use Promises in a recursive manner to solve a maze:

 

Button {
    text: qsTr("Solve Maze")

    onClicked: {
        _asyncToGenerator( function*() {
            function solve(x, y) {
                return _asyncToGenerator( function*() {
                    maze[y][x] = someDude
                    printDaMaze()
                    yield sleep(100)
                    if (x === endingPoint[0] && y === endingPoint[1]) return true
                    if (x > 0 && maze[y][x - 1] === free && (yield solve(x - 1, y))) return true
                    if (x < mazeWidth && maze[y][x + 1] === free && (yield solve(x + 1, y))) return true
                    if (y > 0 && maze[y - 1][x] === free && (yield solve(x, y - 1))) return true
                    if (y < mazeHeight && maze[y + 1][x] === free && (yield solve(x, y + 1))) return true
                    maze[y][x] = free
                    printDaMaze()
                    yield sleep(100)
                    return false
                } )()
            }
            if (yield solve( startingPoint[0], startingPoint[1]) ) {
                console.log( "Solved!" )
                printDaMaze()
            } else {
                console.log( "Cannot solve. :-(" )
            }
        } )()
    }
}

View the full MazeSolver.qml on GitHub. Try it now on AppStudioBlogIterators | WebAssembly.

 

 

 

8. Experimental WebAssembly

 

 

Qt now offers a WebAssembly target.

 

This means it is possible to deploy QML applications in a web browser. WebAssembly is an experimental technology meaning it's an evolving platform. Expect a lot of things to not work. With the disclaimer out of the way, you can test all of the code snippets in this blog in a Web Assembly application here: https://stephenquan.github.io/AppStudio/Apps/AppStudioBlogIterators/

 

 

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.

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.

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

 

 

 

 

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.