Kostadin Markov (18) [Avatar] Offline
#1
Hello

I think there's something wrong with the autocomplete example. It just doesn't work. What I can tell for now is that I am using the latest version of autocomplete.html and autocomplete.js from github. I tried all major browsers - FF, IE and Chrome. Nothing gets popped up when I enter "Gro" or any other known city.

With regards,
Kostadin
Stephen Blackheath (119) [Avatar] Offline
#2
Kostadin,

It works for me on Firefox and Chromium on Ubuntu. Can you make sure you also have the file rx.all.min.js in the same directory, and have a look in the web console for any Javascript error messages?


Steve
Kostadin Markov (18) [Avatar] Offline
#3
Ok, today it works - it might have been some momentous lack of service from geobytes smilie

Best regards,
Kostadin
Kostadin Markov (18) [Avatar] Offline
#4
Ok, guys, here is my version of the autocomplete example. It is slightly different from what's on github.

I just want ask the authors whether I identified the FRP logic and the IO correctly. Also, isn't this way of making up a cell out of input field text stream better - see function currentTextOf(input) implementation(row 46 on)?! I am just strictly going away of using external state. Although, in this particular situation it seems that it doesn't matter. The same goes for defining the sClicked stream at row 76.

Others may take a look at the comments, which could help understand what's going on. Note that functions excapeHTML(text) and calm(s) are shortened(same as those from github).

Autocomplete.html:
<!DOCTYPE html>

<html>
<head>
    <title>Autocomplete - Rx.JS</title>
    <style>
        #info { padding-top: 20px; }
        #city { width: 300px; }
        table { border-collapse: collapse; }
        table td { padding: 2px; border: 1px solid black; }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.min.js"></script>
    <script src="autocomplete.js"></script>
</head>

<body onload="init()">

    <div>
        <label for="city">City</label>
        <input id="city" type="text" />
    </div>
    <div id="info"></div>

</body>
</html>


Autocomplete.js:

var jsonpCallbacks = {
    cntr: 0
};

//creates a stream which would post a cross-site request on the specified url,
//triggered by sRequest(string stream) and using its value as request parameter
//i.e Look up city name on server(geobytes)
function lookup(url, sRequest) {
    //Construct our own hot observable to FRPify the I/O
    var sResponse = Rx.Observable.create(function(observer) {
        return sRequest.subscribe(function(req) {
            //generate new function name for each request event
            //This is I/O so we are allowed to be stateful
            var fnName = "fn" + jsonpCallbacks.cntr++,
                //script tag that would fire the cross-site request
                script = document.createElement("script");
            script.type = "text/javascript";
             //pass the callback function for the response
            script.src = url + encodeURIComponent(req) +
                "&callback=jsonpCallbacks." + fnName;
            //callback function for the response
            jsonpCallbacks[fnName] = function(resp) {
                //callback function not needed any more
                delete jsonpCallbacks[fnName];
                //as well as the script tag from the DOM
                document.body.removeChild(script);
                //firing the response event
                observer.onNext([req, resp]);
            };
            //adding the script tag to the DOM fires the request
            document.body.appendChild(script);
        });
    }).publish();           //requered call, which in combination with
    sResponse.connect();    //the other required call
                            //make the magic called 'single output stream'
    
    return sResponse;
}

function escapeHTML(text) {

//A common FRP idiom: Suppress updates that are the same as previous values
function calm(s) {

//From the add example: BehaviorSubject giving the current text of an input field
function currentTextOf(input) {
    var sKeyPresses = Rx.Observable.fromEvent(input, "keyup");
    var text = new Rx.BehaviorSubject(input.value);
    //sCurrentText defined using a clean from external state function
    var sCurrentText = sKeyPresses.map(function(e) {
        return e.target.value;//input.value
    });
    sCurrentText.subscribe(text);
    return text;
}

//Adding <!DOCTYPE html> to the top autocomplete.html lead to offset values not compatible with the style's dimension properties
//So this function is just a dumb stringification plus adding "px" at the end
function numberToPixelsText(num) {
    return num + "px";
}
//retruns the stream of typed and/or autocompleted cities from an input field 
function autocomplete(textEdit) {
    //IO - construct the popup and add it hidden
    var popup = document.createElement('select');
    popup.size = 15;
    popup.style.position = 'absolute';
    popup.style.zIndex = 100;
    popup.style.display = 'none';
    popup.style.width = numberToPixelsText(textEdit.offsetWidth);
    popup.setAttribute('id', 'popup');
    document.body.appendChild(popup);

    //FRP
    //City names selected from the pop-up
    var sClicked = Rx.Observable.fromEvent(popup, 'change')
                                .map(function(e) {
        //not breaking the rules again
        return e.target.value;//popup.value;
    });
    //Poke those into the text field
    sClicked.subscribe(function(text) {
        return textEdit.value = text;
    });
    var editText = currentTextOf(textEdit),
        sKeyPresses = Rx.Observable.fromEvent(textEdit, 'keyup'),
        //Fire if key presses are idle for 100 ms
        sDebounced = sKeyPresses.startWith(null).debounce(100),
        sTextUpdate = calm(sDebounced.withLatestFrom(editText,
            function(key, text) { return text; }));
    var sTabKey = sKeyPresses.filter(function(k) {
            return k.keyCode == 9; }),
        sEscapeKey = sKeyPresses.filter(function(k) {
            return k.keyCode == 27; }),
        sEnterKey = sKeyPresses.filter(function(k) {
            return k.keyCode == 13; });
    //Clear the pop-up if ESC or ENTER pressed or user selects from the pop-up
    var sClearPopup = sEscapeKey.merge(sEnterKey)
                                .merge(sClicked).map(null);
    //Look up on key presses idle or TAB key
    var lookedUp = lookup("http://gd.geobytes.com/AutoCompleteCity?q=",
        sTextUpdate.merge(sTabKey.withLatestFrom(editText,
            function(key, text) {
                return text;
            }
        ))
        ).map(function(req_resp) { // Handle some "empty response" cases from the server
            var req = req_resp[0],
                resp = req_resp[1];
            return resp.length == 1 && (resp[0] == "%s"
                || resp[0] == "" || resp[0] == req) ? null : resp;
        }).merge(sClearPopup).startWith(null);
    
    //IO - Show or don't show pop-up based on lookedUp
    lookedUp.subscribe(function(items) {
        if (items !== null) {
            var html = '';
            for (var i = 0; i < items.length; i++) {
                html += '<option>' + escapeHTML(items[i]) + '</option>';
            }
            popup.innerHTML = html;
            if (popup.style.display != 'block') {
                popup.style.left = numberToPixelsText(textEdit.offsetLeft);
                popup.style.top = numberToPixelsText(textEdit.offsetTop + textEdit.offsetHeight);
                popup.style.display = 'block';
            }
        }
        else {
            popup.style.display = 'none';
        }
    });
    
    //FRP
    return sEnterKey.withLatestFrom(editText, function(key, text) {
        return text;
    }).merge(sClicked);
}

function init() {
    var cityInput = document.getElementById("city"),
        infoDiv = document.getElementById("info"),
        sEntered = autocomplete(cityInput);
    //Look up city info
    lookup("http://getcitydetails.geobytes.com/GetCityDetails?fqcn=",
           sEntered).subscribe(function(city_info) {
            var city = city_info[0],
                info = city_info[1];
            var html = 'Information for <b>' + escapeHTML(city) +
                       '</b>' + '<table>';
            for (var key in info) {
                html += '<tr><td>' + escapeHTML(key) + '</td><td>' +
                    escapeHTML(info[key]) + '</td></tr>';
            }
            html += '</table>';
            infoDiv.innerHTML = html;
        });
}


I think this could be made even better. I can't tell whether this would look good in terms of FRP, but the autocomplete(textEdit) function could be generalized, making it have the url of the service as a parameter. One other thing is that the popup isn't responding to the keyboard's up/down keys.
Anyways, I am heading towards Elm now and am so curious how all this could be done there. Let the ES6 standardization people wander whether or not they should include weak references smilie

Cheers,
Kostadin
537023 (1) [Avatar] Offline
#5
Thank you so much for this awesome help.
imessage for pc
imessage for android
558212 (1) [Avatar] Offline
#6
Firefox not letting in this site (kik for pc)