<commit_before>/*
    JavaScript autoComplete v2.0.0

    Forked: 13 oct 2017
    GitHub: https://github.com/stephanmahieu/JavaScript-autoComplete

    This adaptation is specifically tailored for use by a Firefox/Chrome plugin.

    Original version:
    Copyright (c) 2014 Simon Steinberger / Pixabay
    GitHub : https://github.com/Pixabay/JavaScript-autoComplete
    License: http://www.opensource.org/licenses/mit-license.php
*/

class AutoComplete {

    constructor(customOptions) {
        const options = {
            selector: 0,
            source: 0,
            minChars: 3,
            delay: 150,
            offsetLeft: 0,
            offsetTop: 1,
            minWidth: 100,
            cache: 1,
            menuClass: '',
            renderItem: (item, search) => {
                // escape special characters
                search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
                const re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");

                const div = document.createElement('div');
                div.classList.add('autocomplete-suggestion');
                div.setAttribute('data-val', item);

                if (search) {
                    search = search.toLowerCase();
                    item.split(re).forEach(term => {
                        if (term.toLowerCase() === search) {
                            let b = document.createElement('b');
                            b.appendChild(document.createTextNode(term));
                            div.appendChild(b);
                        } else if (term) {
                            div.appendChild(document.createTextNode(term));
                        }
                    });
                } else {
                    div.appendChild(document.createTextNode(item));
                }
                // '
' + item.replace(re, "$1") + '
';
                return div;
            },
            onSelect: (e, term, item) => {}
        };

        // add custom options to defaultOptions
        for (let k in customOptions) {
            if (customOptions.hasOwnProperty(k)) {
                options[k] = customOptions[k];
            }
        }

        // init
        this.elems = typeof options.selector === 'object' ? [options.selector] : document.querySelectorAll(options.selector);
        this.elems.forEach(elem => {
            // create suggestions container "sc"
            elem.sc = document.createElement('div');
            elem.sc.classList.add('autocomplete-suggestions');
            if (options.menuClass) {
                elem.sc.classList.add(options.menuClass);
            }

            elem.autocompleteAttr = elem.getAttribute('autocomplete');
            elem.setAttribute('autocomplete', 'off');
            elem.cache = {};
            elem.last_val = '';

            elem.updateSC = (resize, next) => {
                const rect = elem.getBoundingClientRect();
                elem.sc.style.left = Math.round(rect.left + (window.pageXOffset || document.documentElement.scrollLeft) + options.offsetLeft) + 'px';
                elem.sc.style.top = Math.round(rect.bottom + (window.pageYOffset || document.documentElement.scrollTop) + options.offsetTop) + 'px';
                elem.sc.style.width = Math.max(options.minWidth, Math.round(rect.right - rect.left)) + 'px'; // outerWidth (SJM: minimum width 100px)
                if (!resize) {
                    elem.sc.style.display = 'block';
                    if (!elem.sc.maxHeight) {
                        elem.sc.maxHeight = parseInt((window.getComputedStyle ? getComputedStyle(elem.sc, null) : elem.sc.currentStyle).maxHeight);
                    }
                    if (!elem.sc.suggestionHeight) {
                        elem.sc.suggestionHeight = elem.sc.querySelector('.autocomplete-suggestion').offsetHeight;
                    }
                    if (elem.sc.suggestionHeight) {
                        if (!next) {
                            elem.sc.scrollTop = 0;
                        } else {
                            const scrTop = elem.sc.scrollTop, selTop = next.getBoundingClientRect().top - elem.sc.getBoundingClientRect().top;
                            if (selTop + elem.sc.suggestionHeight - elem.sc.maxHeight > 0) {
                                elem.sc.scrollTop = selTop + elem.sc.suggestionHeight + scrTop - elem.sc.maxHeight;
                            }
                            else if (selTop < 0) {
                                elem.sc.scrollTop = selTop + scrTop;
                            }
                        }
                    }
                }
            };
            this._addEvent(window, 'resize', elem.updateSC);
            document.body.appendChild(elem.sc);

            this._live('autocomplete-suggestion', 'mouseleave', () => {
                const sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                if (sel) {
                    setTimeout(()=>{ sel.classList.remove('selected'); }, 20);
                }
            }, elem.sc);

            this._live('autocomplete-suggestion', 'mouseover', event => {
                const sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                if (sel) {
                    sel.classList.remove('selected');
                }
                // event.target might be the ... part, in that case we need the parent
                const item = this._getSuggestionNode(event.target);
                item.classList.add('selected');
            }, elem.sc);

            this._live('autocomplete-suggestion', 'mousedown', event => {
                // event.target might be the ... part, in that case we need the parent
                const item = this._getSuggestionNode(event.target);
                if (item.classList.contains('autocomplete-suggestion')) { // else outside click
                    const v = item.getAttribute('data-val');
                    elem.value = v;
                    options.onSelect(event, v, item);
                    elem.sc.style.display = 'none';
                }
            }, elem.sc);

            this._live('autocomplete-suggestion', 'touchstart', event => {
                // event.target might be the ... part, in that case we need the parent
                const item = this._getSuggestionNode(event.target);
                if (item.classList.contains('autocomplete-suggestion')) { // else outside touch
                    const v = item.getAttribute('data-val');
                    elem.value = v;
                    options.onSelect(event, v, item);
                    elem.sc.style.display = 'none';
                }
            }, elem.sc);

            elem.blurHandler = () => {
                let over_sb;
                try {
                    over_sb = document.querySelector('.autocomplete-suggestions:hover');
                } catch(e) {
                    over_sb = 0;
                }
                if (!over_sb) {
                    elem.last_val = elem.value;
                    elem.sc.style.display = 'none';
                    setTimeout(()=>{ elem.sc.style.display = 'none'; }, 350); // hide suggestions on fast input
                } else if (elem !== document.activeElement) {
                    setTimeout(()=>{ elem.focus(); }, 20);
                }
            };
            this._addEvent(elem, 'blur', elem.blurHandler);

            const suggest = data => {
                const val = elem.value;
                elem.cache[val] = data;
                if (data.length && val.length >= options.minChars) {
                    while (elem.sc.firstChild) {
                        elem.sc.removeChild(elem.sc.firstChild);
                    }
                    data.forEach(item => {
                        elem.sc.appendChild(options.renderItem(item, val));
                    });
                    elem.updateSC(0);
                }
                else {
                    elem.sc.style.display = 'none';
                }
            };

            elem.keydownHandler = event => {
                const key = window.event ? event.keyCode : event.which;
                // down (40), up (38)
                if ((key === 40 || key === 38) && elem.sc.innerHTML) {
                    let next, sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                    if (!sel) {
                        next = (key === 40) ? elem.sc.querySelector('.autocomplete-suggestion') : elem.sc.childNodes[elem.sc.childNodes.length - 1]; // first : last
                        next.classList.add('selected');
                        elem.value = next.getAttribute('data-val');
                    } else {
                        next = (key === 40) ? sel.nextSibling : sel.previousSibling;
                        if (next) {
                            sel.classList.remove('selected');
                            next.classList.add('selected');
                            elem.value = next.getAttribute('data-val');
                        }
                        else {
                            sel.classList.remove('selected');
                            elem.value = elem.last_val; next = 0;
                        }
                    }
                    elem.updateSC(0, next);
                    return false;
                }
                // down arrow with purpose to display suggestions
                else if (key === 40 && !elem.sc.innerHTML) {
                    console.log('Please display SC choices');
                    options.source(elem.value, elem, suggest);
                }
                // esc
                else if (key === 27) {
                    elem.value = elem.last_val;
                    elem.sc.style.display = 'none';
                }
                // enter or tab
                else if (key === 13 || key === 9) {
                    const sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                    if (sel && elem.sc.style.display !== 'none') {
                        options.onSelect(event, sel.getAttribute('data-val'), sel);
                        setTimeout(()=>{ elem.sc.style.display = 'none'; }, 20);
                    }
                }
            };
            this._addEvent(elem, 'keydown', elem.keydownHandler);

            elem.keyupHandler = event => {
                const key = window.event ? event.keyCode : event.which;
                if (!key || (key < 35 || key > 40) && key !== 13 && key !== 27 && key !== 16) {
                    const val = elem.value;
                    if (val.length >= options.minChars) {
                        if (key === 9 && !elem.sc.innerHTML) {
                            // tabbing into a field should not immediately display suggestions
                            elem.last_val = val;
                            return;
                        }
                        if (val !== elem.last_val) {
                            elem.last_val = val;
                            clearTimeout(elem.timer);
                            if (options.cache) {
                                if (val in elem.cache) {
                                    suggest(elem.cache[val]);
                                    return;
                                }
                                // no requests if previous suggestions were empty
                                for (let i=1; i<val.length-options.minChars; i++) {
                                    let part = val.slice(0, val.length-i);
                                    if (part in elem.cache && !elem.cache[part].length) {
                                        suggest([]);
                                        return;
                                    }
                                }
                            }
                            elem.timer = setTimeout(()=>{ options.source(val, elem, suggest) }, options.delay);
                        }
                    } else {
                        elem.last_val = val;
                        elem.sc.style.display = 'none';
                    }
                }
            };
            this._addEvent(elem, 'keyup', elem.keyupHandler);

            elem.focusHandler = event => {
                elem.last_val = '\n';
                elem.keyupHandler(event)
            };
            if (!options.minChars) {
                this._addEvent(elem, 'focus', elem.focusHandler);
            }

            elem.mouseDownHandler = event => {
                // toggle, only display choices every other click
                if (elem.sc) {
                    if (!elem.sc.style.display || elem.sc.style.display === 'none') {
                        elem.last_val = '\n';
                        elem.keyupHandler(event)
                    } else {
                        elem.sc.style.display = 'none';
                    }
                }
            };
            this._addEvent(elem, 'mousedown', elem.mouseDownHandler);
        });
    }

    // public destroy method
    destroy() {
        this.elems.forEach(elem => {
            this._removeEvent(window, 'resize', elem.updateSC);
            this._removeEvent(elem, 'blur', elem.blurHandler);
            this._removeEvent(elem, 'focus', elem.focusHandler);
            this._removeEvent(elem, 'keydown', elem.keydownHandler);
            this._removeEvent(elem, 'keyup', elem.keyupHandler);
            this._removeEvent(elem, 'mousedown', elem.mouseDownHandler);
            if (elem.autocompleteAttr) {
                elem.setAttribute('autocomplete', elem.autocompleteAttr);
            }
            else {
                elem.removeAttribute('autocomplete');
            }
            document.body.removeChild(elem.sc);
            elem = null;
        });
    }

    // helpers
    _addEvent(el, type, handler) {
        el.addEventListener(type, handler);
    }
    _removeEvent(el, type, handler) {
        el.removeEventListener(type, handler);
    }
    _live(elClass, eventType, cb, context){
        this._addEvent(context || document, eventType, event => {
            let found, el = event.target || event.srcElement;
            while (el && !(found = el.classList.contains(elClass))) {
                el = el.parentElement;
            }
            if (found) {
                cb.call(el, event);
            }
        });
    }
    _getSuggestionNode(element) {
        while (element && !element.classList.contains('autocomplete-suggestion')) {
            element = element.parentNode;
        }
        return element;
    }
}
<commit_msg>prevent submit when selecting a value<commit_after>/*
    JavaScript autoComplete v2.0.0

    Forked: 13 oct 2017
    GitHub: https://github.com/stephanmahieu/JavaScript-autoComplete

    This adaptation is specifically tailored for use by a Firefox/Chrome plugin.

    Original version:
    Copyright (c) 2014 Simon Steinberger / Pixabay
    GitHub : https://github.com/Pixabay/JavaScript-autoComplete
    License: http://www.opensource.org/licenses/mit-license.php
*/

class AutoComplete {

    constructor(customOptions) {
        const options = {
            selector: 0,
            source: 0,
            minChars: 3,
            delay: 150,
            offsetLeft: 0,
            offsetTop: 1,
            minWidth: 100,
            cache: 1,
            menuClass: '',
            renderItem: (item, search) => {
                // escape special characters
                search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
                const re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");

                const div = document.createElement('div');
                div.classList.add('autocomplete-suggestion');
                div.setAttribute('data-val', item);

                if (search) {
                    search = search.toLowerCase();
                    item.split(re).forEach(term => {
                        if (term.toLowerCase() === search) {
                            let b = document.createElement('b');
                            b.appendChild(document.createTextNode(term));
                            div.appendChild(b);
                        } else if (term) {
                            div.appendChild(document.createTextNode(term));
                        }
                    });
                } else {
                    div.appendChild(document.createTextNode(item));
                }
                // '
' + item.replace(re, "$1") + '
';
                return div;
            },
            onSelect: (e, term, item) => {}
        };

        // add custom options to defaultOptions
        for (let k in customOptions) {
            if (customOptions.hasOwnProperty(k)) {
                options[k] = customOptions[k];
            }
        }

        // init
        this.elems = typeof options.selector === 'object' ? [options.selector] : document.querySelectorAll(options.selector);
        this.elems.forEach(elem => {
            // create suggestions container "sc"
            elem.sc = document.createElement('div');
            elem.sc.classList.add('autocomplete-suggestions');
            if (options.menuClass) {
                elem.sc.classList.add(options.menuClass);
            }

            elem.autocompleteAttr = elem.getAttribute('autocomplete');
            elem.setAttribute('autocomplete', 'off');
            elem.cache = {};
            elem.last_val = '';

            elem.updateSC = (resize, next) => {
                const rect = elem.getBoundingClientRect();
                elem.sc.style.left = Math.round(rect.left + (window.pageXOffset || document.documentElement.scrollLeft) + options.offsetLeft) + 'px';
                elem.sc.style.top = Math.round(rect.bottom + (window.pageYOffset || document.documentElement.scrollTop) + options.offsetTop) + 'px';
                elem.sc.style.width = Math.max(options.minWidth, Math.round(rect.right - rect.left)) + 'px'; // outerWidth (SJM: minimum width 100px)
                if (!resize) {
                    elem.sc.style.display = 'block';
                    if (!elem.sc.maxHeight) {
                        elem.sc.maxHeight = parseInt((window.getComputedStyle ? getComputedStyle(elem.sc, null) : elem.sc.currentStyle).maxHeight);
                    }
                    if (!elem.sc.suggestionHeight) {
                        elem.sc.suggestionHeight = elem.sc.querySelector('.autocomplete-suggestion').offsetHeight;
                    }
                    if (elem.sc.suggestionHeight) {
                        if (!next) {
                            elem.sc.scrollTop = 0;
                        } else {
                            const scrTop = elem.sc.scrollTop, selTop = next.getBoundingClientRect().top - elem.sc.getBoundingClientRect().top;
                            if (selTop + elem.sc.suggestionHeight - elem.sc.maxHeight > 0) {
                                elem.sc.scrollTop = selTop + elem.sc.suggestionHeight + scrTop - elem.sc.maxHeight;
                            }
                            else if (selTop < 0) {
                                elem.sc.scrollTop = selTop + scrTop;
                            }
                        }
                    }
                }
            };
            this._addEvent(window, 'resize', elem.updateSC);
            document.body.appendChild(elem.sc);

            this._live('autocomplete-suggestion', 'mouseleave', () => {
                const sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                if (sel) {
                    setTimeout(()=>{ sel.classList.remove('selected'); }, 20);
                }
            }, elem.sc);

            this._live('autocomplete-suggestion', 'mouseover', event => {
                const sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                if (sel) {
                    sel.classList.remove('selected');
                }
                // event.target might be the ... part, in that case we need the parent
                const item = this._getSuggestionNode(event.target);
                item.classList.add('selected');
            }, elem.sc);

            this._live('autocomplete-suggestion', 'mousedown', event => {
                // event.target might be the ... part, in that case we need the parent
                const item = this._getSuggestionNode(event.target);
                if (item.classList.contains('autocomplete-suggestion')) { // else outside click
                    const v = item.getAttribute('data-val');
                    elem.value = v;
                    options.onSelect(event, v, item);
                    elem.sc.style.display = 'none';
                }
            }, elem.sc);

            this._live('autocomplete-suggestion', 'touchstart', event => {
                // event.target might be the ... part, in that case we need the parent
                const item = this._getSuggestionNode(event.target);
                if (item.classList.contains('autocomplete-suggestion')) { // else outside touch
                    const v = item.getAttribute('data-val');
                    elem.value = v;
                    options.onSelect(event, v, item);
                    elem.sc.style.display = 'none';
                }
            }, elem.sc);

            elem.blurHandler = () => {
                let over_sb;
                try {
                    over_sb = document.querySelector('.autocomplete-suggestions:hover');
                } catch(e) {
                    over_sb = 0;
                }
                if (!over_sb) {
                    elem.last_val = elem.value;
                    elem.sc.style.display = 'none';
                    setTimeout(()=>{ elem.sc.style.display = 'none'; }, 350); // hide suggestions on fast input
                } else if (elem !== document.activeElement) {
                    setTimeout(()=>{ elem.focus(); }, 20);
                }
            };
            this._addEvent(elem, 'blur', elem.blurHandler);

            const suggest = data => {
                const val = elem.value;
                elem.cache[val] = data;
                if (data.length && val.length >= options.minChars) {
                    while (elem.sc.firstChild) {
                        elem.sc.removeChild(elem.sc.firstChild);
                    }
                    data.forEach(item => {
                        elem.sc.appendChild(options.renderItem(item, val));
                    });
                    elem.updateSC(0);
                }
                else {
                    elem.sc.style.display = 'none';
                }
            };

            elem.keydownHandler = event => {
                const key = window.event ? event.keyCode : event.which;
                // down (40), up (38)
                if ((key === 40 || key === 38) && elem.sc.innerHTML) {
                    let next, sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                    if (!sel) {
                        next = (key === 40) ? elem.sc.querySelector('.autocomplete-suggestion') : elem.sc.childNodes[elem.sc.childNodes.length - 1]; // first : last
                        next.classList.add('selected');
                        elem.value = next.getAttribute('data-val');
                    } else {
                        next = (key === 40) ? sel.nextSibling : sel.previousSibling;
                        if (next) {
                            sel.classList.remove('selected');
                            next.classList.add('selected');
                            elem.value = next.getAttribute('data-val');
                        }
                        else {
                            sel.classList.remove('selected');
                            elem.value = elem.last_val; next = 0;
                        }
                    }
                    elem.updateSC(0, next);
                    return false;
                }
                // down arrow with purpose to display suggestions
                else if (key === 40 && !elem.sc.innerHTML) {
                    console.log('Please display SC choices');
                    options.source(elem.value, elem, suggest);
                }
                // esc
                else if (key === 27) {
                    elem.value = elem.last_val;
                    elem.sc.style.display = 'none';
                }
                // enter or tab
                else if (key === 13 || key === 9) {
                    const sel = elem.sc.querySelector('.autocomplete-suggestion.selected');
                    if (sel && elem.sc.style.display !== 'none') {
                        options.onSelect(event, sel.getAttribute('data-val'), sel);
                        setTimeout(()=>{ elem.sc.style.display = 'none'; }, 20);
                        // prevent submit when selecting a value
                        event.preventDefault();
                    }
                }
            };
            this._addEvent(elem, 'keydown', elem.keydownHandler);

            elem.keyupHandler = event => {
                const key = window.event ? event.keyCode : event.which;
                if (!key || (key < 35 || key > 40) && key !== 13 && key !== 27 && key !== 16) {
                    const val = elem.value;
                    if (val.length >= options.minChars) {
                        if (key === 9 && !elem.sc.innerHTML) {
                            // tabbing into a field should not immediately display suggestions
                            elem.last_val = val;
                            return;
                        }
                        if (val !== elem.last_val) {
                            elem.last_val = val;
                            clearTimeout(elem.timer);
                            if (options.cache) {
                                if (val in elem.cache) {
                                    suggest(elem.cache[val]);
                                    return;
                                }
                                // no requests if previous suggestions were empty
                                for (let i=1; i<val.length-options.minChars; i++) {
                                    let part = val.slice(0, val.length-i);
                                    if (part in elem.cache && !elem.cache[part].length) {
                                        suggest([]);
                                        return;
                                    }
                                }
                            }
                            elem.timer = setTimeout(()=>{ options.source(val, elem, suggest) }, options.delay);
                        }
                    } else {
                        elem.last_val = val;
                        elem.sc.style.display = 'none';
                    }
                }
            };
            this._addEvent(elem, 'keyup', elem.keyupHandler);

            elem.focusHandler = event => {
                elem.last_val = '\n';
                elem.keyupHandler(event)
            };
            if (!options.minChars) {
                this._addEvent(elem, 'focus', elem.focusHandler);
            }

            elem.mouseDownHandler = event => {
                // toggle, only display choices every other click
                if (elem.sc) {
                    if (!elem.sc.style.display || elem.sc.style.display === 'none') {
                        elem.last_val = '\n';
                        elem.keyupHandler(event)
                    } else {
                        elem.sc.style.display = 'none';
                    }
                }
            };
            this._addEvent(elem, 'mousedown', elem.mouseDownHandler);
        });
    }

    // public destroy method
    destroy() {
        this.elems.forEach(elem => {
            this._removeEvent(window, 'resize', elem.updateSC);
            this._removeEvent(elem, 'blur', elem.blurHandler);
            this._removeEvent(elem, 'focus', elem.focusHandler);
            this._removeEvent(elem, 'keydown', elem.keydownHandler);
            this._removeEvent(elem, 'keyup', elem.keyupHandler);
            this._removeEvent(elem, 'mousedown', elem.mouseDownHandler);
            if (elem.autocompleteAttr) {
                elem.setAttribute('autocomplete', elem.autocompleteAttr);
            }
            else {
                elem.removeAttribute('autocomplete');
            }
            document.body.removeChild(elem.sc);
            elem = null;
        });
    }

    // helpers
    _addEvent(el, type, handler) {
        el.addEventListener(type, handler);
    }
    _removeEvent(el, type, handler) {
        el.removeEventListener(type, handler);
    }
    _live(elClass, eventType, cb, context){
        this._addEvent(context || document, eventType, event => {
            let found, el = event.target || event.srcElement;
            while (el && !(found = el.classList.contains(elClass))) {
                el = el.parentElement;
            }
            if (found) {
                cb.call(el, event);
            }
        });
    }
    _getSuggestionNode(element) {
        while (element && !element.classList.contains('autocomplete-suggestion')) {
            element = element.parentNode;
        }
        return element;
    }
}
<|endoftext|>


<commit_before>var calculateGrid = function(map) {
  if (map.interaction_grid) return;
  var interactive_tiles = $('.interactive-div-' + map.getZoom() + ' img', map.d);
  var start_offset = $(map.d).offset();
  // Naive implementation - optimize soon.
  var tiles = $(interactive_tiles).map(function(t) {
    var e_offset = $(interactive_tiles[t]).offset();
    return {
        xy: {
            left: e_offset.left - start_offset.left,
            top: e_offset.top - start_offset.top
            // left: e_offset.left,
            // top:  e_offset.top
        },
        tile: interactive_tiles[t]
    };
  });
  return tiles;
};

var invalidateGrid = function(map) {
  map.interaction_grid = false;
};

var inTile = function(sevt, xy) {
    if ((xy.top < sevt.y) &&
        ((xy.top + 256) > sevt.y) &&
         (xy.left < sevt.x) &&
        ((xy.left + 256) > sevt.x)) {
        return true;
    }
};

var makeInteraction = function(map) {
  var gm = new GridManager();
  var f = null;
  var options = { format: 'teaser' };
  google.maps.event.addListener(map, 'mousemove', function(evt) {
    var found = false;
    var interaction_grid = calculateGrid(map);
    for (var i = 0; i < interaction_grid.length && !found; i++) {
      if (inTile(evt.pixel, interaction_grid[i].xy)) {
          var found = interaction_grid[i];
      }
    }
    if (found) {
      gm.getGrid($(found.tile).attr('src'), function(g) {
        if (g) {
          var feature = g.getFeature(
            evt.pixel.x + $(map.d).offset().left,
            evt.pixel.y + $(map.d).offset().top,
            found.tile,
            options
          );
          if (feature !== f) {
            MapTooltips.unselect(feature, $(map.d).parent(), 0);
            MapTooltips.select(feature, $(map.d).parent(), 0);
            f = feature;
          }
        }
      });
    }
  });
  /*
  google.maps.event.addListener(map, 'bounds_changed', function(evt) {
    invalidateGrid(map);
  });
  */
};
<commit_msg>Add popup handling for google maps interaction.<commit_after>var calculateGrid = function(map) {
  if (map.interaction_grid) return;
  var interactive_tiles = $('.interactive-div-' + map.getZoom() + ' img', map.d);
  var start_offset = $(map.d).offset();
  // Naive implementation - optimize soon.
  var tiles = $(interactive_tiles).map(function(t) {
    var e_offset = $(interactive_tiles[t]).offset();
    return {
        xy: {
            left: e_offset.left - start_offset.left,
            top: e_offset.top - start_offset.top
            // left: e_offset.left,
            // top:  e_offset.top
        },
        tile: interactive_tiles[t]
    };
  });
  return tiles;
};

var invalidateGrid = function(map) {
  map.interaction_grid = false;
};

var inTile = function(sevt, xy) {
    if ((xy.top < sevt.y) &&
        ((xy.top + 256) > sevt.y) &&
         (xy.left < sevt.x) &&
        ((xy.left + 256) > sevt.x)) {
        return true;
    }
};

var makeInteraction = function(map) {
  var gm = new GridManager();
  var f = null;
  var find = function(map, evt) {
    var found = false;
    var interaction_grid = calculateGrid(map);
    for (var i = 0; i < interaction_grid.length && !found; i++) {
      if (inTile(evt.pixel, interaction_grid[i].xy)) {
          var found = interaction_grid[i];
      }
    }
    return found;
  };
  google.maps.event.addListener(map, 'mousemove', function(evt) {
    var options = { format: 'teaser' };
    var found = find(map, evt);
    if (!found) return;
    gm.getGrid($(found.tile).attr('src'), function(g) {
        if (!g) return;
        var feature = g.getFeature(
          evt.pixel.x + $(map.d).offset().left,
          evt.pixel.y + $(map.d).offset().top,
          found.tile,
          options
        );
        if (feature !== f) {
          MapTooltips.unselect(feature, $(map.d), 0);
          MapTooltips.select(feature, $(map.d), 0);
          f = feature;
        }
    });
  });
  google.maps.event.addListener(map, 'click', function(evt) {
    var options = { format: 'full' };
    var found = find(map, evt);
    if (!found) return;
    gm.getGrid($(found.tile).attr('src'), function(g) {
        if (!g) return;
        var feature = g.getFeature(
          evt.pixel.x + $(map.d).offset().left,
          evt.pixel.y + $(map.d).offset().top,
          found.tile,
          options
        );
        feature && MapTooltips.click(feature, $(map.d), 0);
    });
  });
};
<|endoftext|>


<commit_before>steal('can/control', function(){

//used to determine if a controller instance is one of controllers
//controllers can be strings or classes
var i,
	isAControllerOf = function( instance, controllers ) {
		for ( i = 0; i < controllers.length; i++ ) {
			if ( typeof controllers[i] == 'string' ? instance.constructor._shortName == controllers[i] : instance instanceof controllers[i] ) {
				return true;
			}
		}
		return false;
	},
	data = function(el, data){
		var $el = can.$(el);
		if(!$el.data('controllers')) {
			$el.data('controllers', data || {})
		}
		return $el.data('controllers');
	},
	makeArray = can.makeArray,
	old = can.Control.setup;

can.Control.setup = function() {
	// if you didn't provide a name, or are control, don't do anything
	if ( this !== can.Control ) {

		var pluginName = this.pluginName || this._fullName;

		// create can plugin
		if(pluginName !== 'can_control'){
			this.plugin(pluginName);
		}

		old.apply(this, arguments);
	}
};

can.prototype.extend({

	controllers: function() {
		var controllerNames = makeArray(arguments),
			instances = [],
			controllers, c, cname;
		//check if arguments
		this.each(function() {

			controllers = data(this);
			for ( cname in controllers ) {
				if ( controllers.hasOwnProperty(cname) ) {
					c = controllers[cname];
					if (!controllerNames.length || isAControllerOf(c, controllerNames) ) {
						instances.push(c);
					}
				}
			}
		});
		return instances;
	},

	controller: function( controller ) {
		// Just return the first result from the .controllers() list
		return this.controllers.apply(this, arguments)[0];
	}
});

can.Control.plugin = function(pluginname){
	var controller = this;

	if (!can.prototype[pluginname]) {
		can.prototype[pluginname] = function(options){
			var args = makeArray(arguments),   //if the arg is a method on this controller
			isMethod = typeof options == "string" && $.isFunction(controller.prototype[options]), meth = args[0];
			return this.each(function(){
				//check if created
				var controllers = data(this),    //plugin is actually the controller instance
				plugin = controllers && controllers[pluginname];
				if (plugin) {
					if (isMethod) {
						// call a method on the controller with the remaining args
						plugin[meth].apply(plugin, args.slice(1));
					}
					else {
						// call the plugin's update method
						plugin.update.apply(plugin, args);
					}

				}
				else {
					//create a new controller instance
					controllers[pluginname] =
						controller.newInstance.apply(controller, [this].concat(args));
				}
			});
		};
	}
}

// Add the update method to the can.Control prototype
can.Control.prototype.update = function( options ) {
	can.extend(this.options, options);
	this.on();
};

});<commit_msg>inclusion of update to docs<commit_after>steal('can/control', function(){

//used to determine if a controller instance is one of controllers
//controllers can be strings or classes
var i,
	isAControllerOf = function( instance, controllers ) {
		for ( i = 0; i < controllers.length; i++ ) {
			if ( typeof controllers[i] == 'string' ? instance.constructor._shortName == controllers[i] : instance instanceof controllers[i] ) {
				return true;
			}
		}
		return false;
	},
	data = function(el, data){
		var $el = can.$(el);
		if(!$el.data('controllers')) {
			$el.data('controllers', data || {})
		}
		return $el.data('controllers');
	},
	makeArray = can.makeArray,
	old = can.Control.setup;

can.Control.setup = function() {
	// if you didn't provide a name, or are control, don't do anything
	if ( this !== can.Control ) {

		var pluginName = this.pluginName || this._fullName;

		// create can plugin
		if(pluginName !== 'can_control'){
			this.plugin(pluginName);
		}

		old.apply(this, arguments);
	}
};

can.prototype.extend({

	controllers: function() {
		var controllerNames = makeArray(arguments),
			instances = [],
			controllers, c, cname;
		//check if arguments
		this.each(function() {

			controllers = data(this);
			for ( cname in controllers ) {
				if ( controllers.hasOwnProperty(cname) ) {
					c = controllers[cname];
					if (!controllerNames.length || isAControllerOf(c, controllerNames) ) {
						instances.push(c);
					}
				}
			}
		});
		return instances;
	},

	controller: function( controller ) {
		// Just return the first result from the .controllers() list
		return this.controllers.apply(this, arguments)[0];
	}
});

can.Control.plugin = function(pluginname){
	var controller = this;

	if (!can.prototype[pluginname]) {
		can.prototype[pluginname] = function(options){
			var args = makeArray(arguments),   //if the arg is a method on this controller
			isMethod = typeof options == "string" && $.isFunction(controller.prototype[options]), meth = args[0];
			return this.each(function(){
				//check if created
				var controllers = data(this),    //plugin is actually the controller instance
				plugin = controllers && controllers[pluginname];
				if (plugin) {
					if (isMethod) {
						// call a method on the controller with the remaining args
						plugin[meth].apply(plugin, args.slice(1));
					}
					else {
						// call the plugin's update method
						plugin.update.apply(plugin, args);
					}

				}
				else {
					//create a new controller instance
					controllers[pluginname] =
						controller.newInstance.apply(controller, [this].concat(args));
				}
			});
		};
	}
}

// Add the update method to the can.Control prototype
/**
 * @function can.Control.prototype.update
 * @parent can.Control.plugin
 */
can.Control.prototype.update = function( options ) {
	can.extend(this.options, options);
	this.on();
};

});<|endoftext|>


<commit_before>
var React = require('react/addons'),
	debug = require('debug')('triton:ClientController'),
	RequestContext = require('./context/RequestContext'),
	Q = require('q'),
	cssHelper = require('./util/ClientCssHelper'),
	EventEmitter = require("events").EventEmitter,
	ClientRequest = require("./ClientRequest"),
	History = require('./components/History'),
	PageUtil = require("./util/PageUtil");

// for dev tools
window.React = React;

var TRITON_DATA_ATTRIBUTE = "data-triton-root-id";

class ClientController extends EventEmitter {

	constructor ({routes, dehydratedState, mountNode}) {

		checkNotEmpty(dehydratedState, 'InitialContext');
		checkNotEmpty(dehydratedState, 'Config');

		this.config = buildConfig(dehydratedState.Config);
		this.context = buildContext(
			dehydratedState.InitialContext,
			routes
		);
		this.mountNode = mountNode;

		var irDfd = this._initialRenderDfd = Q.defer();
		this.once('render', irDfd.resolve.bind(irDfd));

		this._setupNavigateListener();
		this._setupLateArrivalHandler();

		this._previouslyRendered = false;
	}

	terminate() {
        this._history.off(this._historyListener);
        this._historyListener = null;
        this._history = null;
	}

	_setupNavigateListener () {
		var context = this.context;

	/**
	 * type is one of
	 *    History.events.PUSHSTATE: user clicked something to go forward but browser didn't do a
	 * full page load
	 *    History.events.POPSTATE: user clicked back button but browser didn't do a full page load
	 *    History.events.PAGELOAD: full browser page load, not using History API.
	 */
		context.onNavigate( (err, page, path, type) => {
			debug('Executing navigate action');

			if (err) {
				// redirects are sent as errors, so let's handle it if that's the case.
				if (err.status && (err.status === 301 || err.status === 302)) {
					if (!err.redirectUrl) {
						console.error("A redirect status was sent without a corresponding redirect redirectUrl.", err);
					} else {
						setTimeout(() => this.context.navigate(new ClientRequest(err.redirectUrl)), 0);
					}
				} else {
					debug("There was an error:", err);
					console.error(err);
				}
				return;
			}

			var routeName = context.navigator.getCurrentRoute().name;

			if (!this._previouslyRendered) {
				cssHelper.registerPageLoad(routeName);
			} else {
				// getTitle can return a String or a Promise of String. we wrap with Q here
				// to normalize the result to promise of String.
				Q(page.getTitle()).then(newTitle => {
					if (newTitle && newTitle !== document.title) {
						document.title = newTitle;
					}
				}).catch(err => console.error("Error while setting the document title", err));
			}

			cssHelper.ensureCss(routeName, page);

			// if this is a History.events.PUSHSTATE navigation, we should change the URL in the bar location bar right
			// before rendering.
			// note that for browsers that do not have pushState, this will result in a window.location change
			// and full browser load. It's kind of late to do that, as we may have waited for handleRoute to
			// finish asynchronously. perhaps we should have an "URLChanged" event that happens before "NavigateDone".
	        if (type === History.events.PUSHSTATE && this._history) {
	            this._history.pushState(null, null, path);
	        }

			this._render(page);

		});

	}

	_render (page) {

		// if we were previously rendered on the client, clean up the old divs and
		// their ReactComponents.
		this._cleanupPreviousRender();

		// if we are reattaching to server-generated HTML, we should find the root elements that were sent down.
		// we store them in a temp array indexed by their triton-root-id.
		var serverRenderedRoots = [];
		for (var i = 0; i < this.mountNode.children.length; i++) {
			if (this.mountNode.children[i].hasAttribute(TRITON_DATA_ATTRIBUTE)) {
				serverRenderedRoots[this.mountNode.children[i].getAttribute(TRITON_DATA_ATTRIBUTE)] = this.mountNode.children[i];
			}
		}

		debug('React Rendering');
		var elementPromises = PageUtil.standardizeElements(page.getElements());

		var renderElement = (element, index) => {
			// for each ReactElement that we want to render, either use the server-rendered root element, or
			// create a new root element.
			var root = serverRenderedRoots[index] || this._createTritonRootNode(this.mountNode, index);

			// TODO: get rid of context once continuation-local-storage holds our important context vars.
			element = React.addons.cloneWithProps(element, { context: this.context });
			React.render(element, root);
		};

		// TODO: deal with the timeouts.
		// I learned how to chain an array of promises from http://bahmutov.calepin.co/chaining-promises.html
		return elementPromises.reduce((chain, next, index) => {
			return chain.then((element) => {
		 		renderElement(element, index - 1);
				return next;
			})
		}).then((element) => {
			// reduce is called length - 1 times. we need to call one final time here to make sure we
			// chain the final promise.
	 		renderElement(element, elementPromises.length - 1);

			this._previouslyRendered = true;
			debug('React Rendered');
			this.emit('render');
		});
	}

	/**
	 * Cleans up a previous React render in the document. Unmounts all the components and destoys the mounting
	 * DOM node(s) that were created.
	 */
	_cleanupPreviousRender() {
		if (this._previouslyRendered) {
			debug("Removing previous page's React components");
			// first, copy the children from a node list to an array so that
			// we can remove elements from their parent during the loop without modifying
			// the list we are iterating over.
			var tritonRootsNL = this.mountNode.children;
			var tritonRoots = [];
			for (var i = 0; i < tritonRootsNL.length; i++) {
				tritonRoots[i] = tritonRootsNL[i];
			}

			// Now, for each of the roots, unmount the React component and destroy the DOM node.
			tritonRoots.forEach((tritonRoot) => {
				if (tritonRoot.hasAttribute(TRITON_DATA_ATTRIBUTE)) {
					// since this node has a data-triton-root-id, we can assume that we created it and should
					// destroy it.
					React.unmountComponentAtNode(tritonRoot);
					this.mountNode.removeChild(tritonRoot);
				} else {
					// it's deeply troubling that there's a div we didn't create, but for now, just warn, and
					// don't obliterate the node.
					console.warn("Found an element inside Triton's rendering canvas that did not have data-triton-root-id " +
						"and was probably not created by Triton. Other code may be manually mucking with the DOM, which could " +
						"cause unpredictable behavior", tritonRoot);
				}
			});
		}
	}

	/**
	 * This method creates a new div to render a ReactElement in to at the end of the mount node.
	 */
	_createTritonRootNode(mountNode, index) {
		var root = document.createElement("div");
		root.setAttribute(TRITON_DATA_ATTRIBUTE, index);
		mountNode.appendChild(root);
		return root;
	}

	init () {
		var location = window.location;
		var path = location.pathname + location.search;
		this._initializeHistoryListener(this.context);
		this.context.navigate(new ClientRequest(path));
	}

	/**
	 * Initializes us to listen to back button events. When the user presses the back button, the history
	 * listener will be called and cause a navigate() event.
	 */
	_initializeHistoryListener(context) {

        this._historyListener = (e) => {
            if (context) {
                var path = this._history.getPath();

                // REDFIN-TODO: this appears to pass some state. Should we figure out how to replicate that?
                // context.executeAction(navigateAction, {type: History.events.POPSTATE, path: path, params: e.state});

                // pass in "popstate" because this is when a user clicks the back button.
                context.navigate(new ClientRequest(path), History.events.POPSTATE);

            }
        };

        this._history = new History();
        this._history.on(this._historyListener);
	}

	_setupLateArrivalHandler () {
		// used by <script> callbacks to register data sent down on the
		// initial connection after initial render
		window.__lateArrival = this.lateArrival.bind(this);
	}

	lateArrival (url, data) {
		this._initialRenderDfd.promise.done( () => {
			this.context.loader.lateArrival(url, data);
		});
	}

}

function checkNotEmpty(state, key) {
	if (typeof state[key] === 'undefined') {
		var msg = key + ' not defined in dehydrated state';
		debug(msg)
		throw new Error(msg);
	}
}

function buildConfig(dehydratedConfig) {
	// rehydrate the config object
	var config = require("./config")();
	config.rehydrate(dehydratedConfig);
	return config;
}

function buildContext(dehydratedContext, routes) {
	var context = new RequestContext.Builder()
		.setRoutes(routes)
		.create();
	context.rehydrate(dehydratedContext);
	return context;
}

module.exports = ClientController;

<commit_msg>Triton API Refactor: refactor of ClientController to move some functionality to a shared function.<commit_after>
var React = require('react/addons'),
	debug = require('debug')('triton:ClientController'),
	RequestContext = require('./context/RequestContext'),
	Q = require('q'),
	cssHelper = require('./util/ClientCssHelper'),
	EventEmitter = require("events").EventEmitter,
	ClientRequest = require("./ClientRequest"),
	History = require('./components/History'),
	PageUtil = require("./util/PageUtil");

// for dev tools
window.React = React;

var TRITON_DATA_ATTRIBUTE = "data-triton-root-id";

class ClientController extends EventEmitter {

	constructor ({routes, dehydratedState, mountNode}) {

		checkNotEmpty(dehydratedState, 'InitialContext');
		checkNotEmpty(dehydratedState, 'Config');

		this.config = buildConfig(dehydratedState.Config);
		this.context = buildContext(
			dehydratedState.InitialContext,
			routes
		);
		this.mountNode = mountNode;

		var irDfd = this._initialRenderDfd = Q.defer();
		this.once('render', irDfd.resolve.bind(irDfd));

		this._setupNavigateListener();
		this._setupLateArrivalHandler();

		this._previouslyRendered = false;
	}

	terminate() {
        this._history.off(this._historyListener);
        this._historyListener = null;
        this._history = null;
	}

	_setupNavigateListener () {
		var context = this.context;

	/**
	 * type is one of
	 *    History.events.PUSHSTATE: user clicked something to go forward but browser didn't do a
	 * full page load
	 *    History.events.POPSTATE: user clicked back button but browser didn't do a full page load
	 *    History.events.PAGELOAD: full browser page load, not using History API.
	 */
		context.onNavigate( (err, page, path, type) => {
			debug('Executing navigate action');

			if (err) {
				// redirects are sent as errors, so let's handle it if that's the case.
				if (err.status && (err.status === 301 || err.status === 302)) {
					if (!err.redirectUrl) {
						console.error("A redirect status was sent without a corresponding redirect redirectUrl.", err);
					} else {
						setTimeout(() => this.context.navigate(new ClientRequest(err.redirectUrl)), 0);
					}
				} else {
					debug("There was an error:", err);
					console.error(err);
				}
				return;
			}

			var routeName = context.navigator.getCurrentRoute().name;

			if (!this._previouslyRendered) {
				cssHelper.registerPageLoad(routeName);
			} else {
				// getTitle can return a String or a Promise of String. we wrap with Q here
				// to normalize the result to promise of String.
				Q(page.getTitle()).then(newTitle => {
					if (newTitle && newTitle !== document.title) {
						document.title = newTitle;
					}
				}).catch(err => console.error("Error while setting the document title", err));
			}

			cssHelper.ensureCss(routeName, page);

			// if this is a History.events.PUSHSTATE navigation, we should change the URL in the bar location bar right
			// before rendering.
			// note that for browsers that do not have pushState, this will result in a window.location change
			// and full browser load. It's kind of late to do that, as we may have waited for handleRoute to
			// finish asynchronously. perhaps we should have an "URLChanged" event that happens before "NavigateDone".
	        if (type === History.events.PUSHSTATE && this._history) {
	            this._history.pushState(null, null, path);
	        }

			this._render(page);

		});

	}

	_render (page) {

		// if we were previously rendered on the client, clean up the old divs and
		// their ReactComponents.
		this._cleanupPreviousRender(this.mountNode);

		// if we are reattaching to server-generated HTML, we should find the root elements that were sent down.
		// we store them in a temp array indexed by their triton-root-id.
		var serverRenderedRoots = [];
		this._getRootElements(this.mountNode).forEach((rootElement) => {
			serverRenderedRoots[rootElement.getAttribute(TRITON_DATA_ATTRIBUTE)] = rootElement;
		});

		debug('React Rendering');
		var elementPromises = PageUtil.standardizeElements(page.getElements());

		var renderElement = (element, index) => {
			// for each ReactElement that we want to render, either use the server-rendered root element, or
			// create a new root element.
			var root = serverRenderedRoots[index] || this._createTritonRootNode(this.mountNode, index);

			// TODO: get rid of context once continuation-local-storage holds our important context vars.
			element = React.addons.cloneWithProps(element, { context: this.context });
			React.render(element, root);
		};

		// TODO: deal with the timeouts.
		// I find the control flow for chaining promises impossibly mind-bending, but what I intended was something
		// like:
		//
		//		elementPromises.forEach((elementPromise, index) => {
		//			var element = await elementPromise;
		//			renderElement(element, index);
		//		});
		//		this._previouslyRendered = true;
		//		this.emit("render");
		//
		// if it was using ES7 async/await syntax. I learned how to chain an array of promises in ES5/6
		// from http://bahmutov.calepin.co/chaining-promises.html , and apparently it looks like the code below. But
		// if I got something wrong, what I intended was the control flow expressed in this comment.
		return elementPromises.reduce((chain, next, index) => {
			return chain.then((element) => {
		 		renderElement(element, index - 1);
				return next;
			})
		}).then((element) => {
			// reduce is called length - 1 times. we need to call one final time here to make sure we
			// chain the final promise.
	 		renderElement(element, elementPromises.length - 1);

			this._previouslyRendered = true;
			debug('React Rendered');
			this.emit('render');
		});
	}

	/**
	 * Cleans up a previous React render in the document. Unmounts all the components and destoys the mounting
	 * DOM node(s) that were created.
	 */
	_cleanupPreviousRender(mountNode) {
		if (this._previouslyRendered) {
			debug("Removing previous page's React components");

			this._getRootElements(mountNode).forEach((tritonRoot) => {
				// since this node has a "data-triton-root-id" attribute, we can assume that we created it and
				// should destroy it. Destruction means first unmounting from React and then destroying the DOM node.
				React.unmountComponentAtNode(tritonRoot);
				mountNode.removeChild(tritonRoot);
			});
		}
	}

	/**
	 * Returns an array of all of the root elements that are children of mountNode. The root elements
	 * should all have a "data-triton-root-id" attribute and be direct children of mountNode.
	 */
	_getRootElements(mountNode) {
		// if children returned an array instead of a NodeList, we could use .filter(), but
		// alas, it does not. We could copy it over to an array, but seems easier to just iterate
		// over the NodeList.
		var potentialRoots = mountNode.children;
		var result = [];
		for (var i = 0; i < potentialRoots.length; i++) {
			var potentialRoot = potentialRoots[i];
			if (potentialRoot.hasAttribute(TRITON_DATA_ATTRIBUTE)) {
				// since this node has a "data-triton-root-id" attribute, we can assume that we created it.
				result.push(potentialRoot);
			} else {
				// it's deeply troubling that there's a div we didn't create, but for now, just warn, and
				// don't obliterate the node.
				console.warn("Found an element inside Triton's rendering canvas that did not have data-triton-root-id " +
					"and was probably not created by Triton. Other code may be manually mucking with the DOM, which could " +
					"cause unpredictable behavior", tritonRoot);
			}
		}
		return result;
	}

	/**
	 * This method creates a new div to render a ReactElement in to at the end of the mount node.
	 */
	_createTritonRootNode(mountNode, index) {
		var root = document.createElement("div");
		root.setAttribute(TRITON_DATA_ATTRIBUTE, index);
		mountNode.appendChild(root);
		return root;
	}

	init () {
		var location = window.location;
		var path = location.pathname + location.search;
		this._initializeHistoryListener(this.context);
		this.context.navigate(new ClientRequest(path));
	}

	/**
	 * Initializes us to listen to back button events. When the user presses the back button, the history
	 * listener will be called and cause a navigate() event.
	 */
	_initializeHistoryListener(context) {

        this._historyListener = (e) => {
            if (context) {
                var path = this._history.getPath();

                // REDFIN-TODO: this appears to pass some state. Should we figure out how to replicate that?
                // context.executeAction(navigateAction, {type: History.events.POPSTATE, path: path, params: e.state});

                // pass in "popstate" because this is when a user clicks the back button.
                context.navigate(new ClientRequest(path), History.events.POPSTATE);

            }
        };

        this._history = new History();
        this._history.on(this._historyListener);
	}

	_setupLateArrivalHandler () {
		// used by <script> callbacks to register data sent down on the
		// initial connection after initial render
		window.__lateArrival = this.lateArrival.bind(this);
	}

	lateArrival (url, data) {
		this._initialRenderDfd.promise.done( () => {
			this.context.loader.lateArrival(url, data);
		});
	}

}

function checkNotEmpty(state, key) {
	if (typeof state[key] === 'undefined') {
		var msg = key + ' not defined in dehydrated state';
		debug(msg)
		throw new Error(msg);
	}
}

function buildConfig(dehydratedConfig) {
	// rehydrate the config object
	var config = require("./config")();
	config.rehydrate(dehydratedConfig);
	return config;
}

function buildContext(dehydratedContext, routes) {
	var context = new RequestContext.Builder()
		.setRoutes(routes)
		.create();
	context.rehydrate(dehydratedContext);
	return context;
}

module.exports = ClientController;

<|endoftext|>


<commit_before>var acornLibsLoaded = false,
    acornLibs = [], dependencies = [],
    isNodeJs = typeof process !== "undefined" && process.version;

(function initLoad() {
    if (isNodeJs) {
        module.require("util")._extend(Global.lively.ast, module.require(process.cwd() + "/node_modules/lively.ast"));
        Global.babel = module.require(process.cwd() + "/node_modules/babel-core");
        Global.escodegen = module.require(process.cwd() + "/node_modules/escodegen");
        acornLibsLoaded = true;
    } else {
        acornLibs = [
            Config.codeBase + 'lib/escodegen.browser.js',
            Config.codeBase + 'lib/lively.ast.dev.js', // pulls in acorn, defines lively.ast
            Config.codeBase + 'lib/babel-browser.js'
        ];
        dependencies = [
            {url: acornLibs[0], loadTest: function() { return typeof escodegen !== 'undefined'; }},
            {url: acornLibs[1], loadTest: function() { return lively.ast && lively.ast.acorn; }},
            {url: acornLibs[2], loadTest: function() { return typeof babel !== 'undefined'; }}
        ];
        module("lively.ast.acorn").requires().requiresLib({urls: acornLibs, loadTest: function() { return !!acornLibsLoaded; }});
        dependencies.doAndContinue(function(next, lib) {
            JSLoader.loadJs(lib.url);
            var interval = Global.setInterval(function() {
                if (!lib.loadTest()) return;
                Global.clearInterval(interval);
                next();
            }, 50);
        }, function() { acornLibsLoaded = true; });
    }
})();

module("lively.ast.acorn").requires().toRun(function() {

// This module is currently kept around while transitioning to the lively.ast lib integration.
// It formerly contained lively's acorn extensions

}); // end of module
<commit_msg>Revert "ast: fix nodejs loading issue introduced in #5c8613c"<commit_after>var acornLibsLoaded = false,
    acornLibs = [], dependencies = [],
    isNodeJs = typeof process !== "undefined" && process.version;

(function initLoad() {
    if (isNodeJs) {
        nodejs.require("util")._extend(Global.lively.ast, nodejs.require(process.cwd() + "/node_modules/lively.ast"));
        Global.babel = nodejs.require(process.cwd() + "/node_modules/babel-core");
        Global.escodegen = nodejs.require(process.cwd() + "/node_modules/escodegen");
        acornLibsLoaded = true;
    } else {
        acornLibs = [
            Config.codeBase + 'lib/escodegen.browser.js',
            Config.codeBase + 'lib/lively.ast.dev.js', // pulls in acorn, defines lively.ast
            Config.codeBase + 'lib/babel-browser.js'
        ];
        dependencies = [
            {url: acornLibs[0], loadTest: function() { return typeof escodegen !== 'undefined'; }},
            {url: acornLibs[1], loadTest: function() { return lively.ast && lively.ast.acorn; }},
            {url: acornLibs[2], loadTest: function() { return typeof babel !== 'undefined'; }}
        ];
        module("lively.ast.acorn").requires().requiresLib({urls: acornLibs, loadTest: function() { return !!acornLibsLoaded; }});
        dependencies.doAndContinue(function(next, lib) {
            JSLoader.loadJs(lib.url);
            var interval = Global.setInterval(function() {
                if (!lib.loadTest()) return;
                Global.clearInterval(interval);
                next();
            }, 50);
        }, function() { acornLibsLoaded = true; });
    }
})();

module("lively.ast.acorn").requires().toRun(function() {

// This module is currently kept around while transitioning to the lively.ast lib integration.
// It formerly contained lively's acorn extensions

}); // end of module
<|endoftext|>


<commit_before>/**
 * @module mapping
 */

var Store = require('./store'),
    SiestaModel = require('./modelInstance'),
    log = require('./operation/log'),
    Operation = require('./operation/operation').Operation,
    InternalSiestaError = require('./error').InternalSiestaError,
    Query = require('./query'),
    cache = require('./cache'),
    util = require('./util'),
    _ = util._,
    async = util.async,
    ChangeType = require('./changes').ChangeType;

var Logger = log.loggerWithName('MappingOperation');

function SiestaError(opts) {
    this.opts = opts;
}
SiestaError.prototype.toString = function () {
    return JSON.stringify(this.opts, null, 4);
};

/**
 * Defines an encapsulated mapping operation where opts takes a mappin
 * @param {Object} opts
 */
function MappingOperation(opts) {
    Operation.call(this);

    this._opts = opts;

    util.extendFromOpts(this, opts, {
        model: null,
        data: null,
        objects: [],
        disableNotifications: false
    });

    //
    ///**
    // * @name mapping
    // * @type {Model}
    // */
    //defineSubProperty.call(this, 'model', this._opts);
    //
    ///**
    // * @name data
    // * @type {Array}
    // */
    //defineSubProperty.call(this, 'data', this._opts);
    //
    ///**
    // * @name objects
    // * @type {Array}
    // */
    //defineSubProperty.call(this, 'objects', this._opts);
    //
    ///**
    // * @name disableNotifications
    // * @type {bool}
    // */
    //defineSubProperty.call(this, 'disableNotifications', this._opts);

    //if (!this.objects) this.objects = [];

    /**
     * Array of errors where indexes map onto same index as the datum that caused an error.
     * @type {Array}
     */
    this.errors = [];

    this.name = 'Model Operation';
    this.work = _.bind(this._start, this);
    this.subOps = {};
}

MappingOperation.prototype = Object.create(Operation.prototype);

_.extend(MappingOperation.prototype, {
    mapAttributes: function () {
        for (var i = 0; i < this.data.length; i++) {
            var datum = this.data[i];
            var object = this.objects[i];
            // No point mapping object onto itself. This happens if a ModelInstance is passed as a relationship.
            if (datum != object) {
                if (object) { // If object is falsy, then there was an error looking up that object/creating it.
                    var fields = this.model._attributeNames;
                    _.each(fields, function (f) {
                        if (datum[f] !== undefined) { // null is fine
                            // If notifications are disabled we update __values object directly. This avoids triggering
                            // notifications which are built into the set function of the property.
                            if (this.disableNotifications) {
                                object.__values[f] = datum[f];
                            }
                            else {
                                object[f] = datum[f];
                            }
                        }
                    }.bind(this));
                    // PouchDB revision (if using storage module).
                    // TODO: Can this be pulled out of core?
                    if (datum._rev) object._rev = datum._rev;
                }
            }
        }
    },
    _map: function () {
        var self = this;
        var err;
        this.mapAttributes();
        var relationshipFields = _.keys(self.subOps);
        _.each(relationshipFields, function (f) {
            var op = self.subOps[f];
            var indexes = op.__indexes;
            var relatedData = self.getRelatedData(f).relatedData;
            var unflattenedObjects = util.unflattenArray(op.objects, relatedData);
            for (var i = 0; i < unflattenedObjects.length; i++) {
                var idx = indexes[i];
                // Errors are plucked from the suboperations.
                var error = self.errors[idx];
                err = error ? error[f] : null;
                if (!err) {
                    var related = unflattenedObjects[i]; // Can be array or scalar.
                    var object = self.objects[idx];
                    if (object) {
                        err = object.__proxies[f].set(related, {disableNotifications: self.disableNotifications});
                        if (err) {
                            if (!self.errors[idx]) self.errors[idx] = {};
                            self.errors[idx][f] = err;
                        }
                    }
                }
            }
        });
    },
    /**
     * For indices where no object is present, perform lookups, creating a new object if necessary.
     * @private
     */
    _lookup: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        var self = this;
        var remoteLookups = [];
        var localLookups = [];
        for (var i = 0; i < this.data.length; i++) {
            if (!this.objects[i]) {
                var lookup;
                var datum = this.data[i];
                var isScalar = typeof datum == 'string' || typeof datum == 'number' || datum instanceof String;
                if (datum) {
                    if (isScalar) {
                        lookup = {
                            index: i,
                            datum: {}
                        };
                        lookup.datum[self.model.id] = datum;
                        remoteLookups.push(lookup);
                    } else if (datum instanceof SiestaModel) { // We won't need to perform any mapping.
                        this.objects[i] = datum;
                    } else if (datum._id) {
                        localLookups.push({
                            index: i,
                            datum: datum
                        });
                    } else if (datum[self.model.id]) {
                        remoteLookups.push({
                            index: i,
                            datum: datum
                        });
                    } else {
                        // Create a new object if and only if the data has any fields that will actually
                        var datumFields = Object.keys(datum);
                        var objectFields = _.reduce(Object.keys(self.model.relationships).concat(self.model._attributeNames), function (m, x) {
                            m[x] = {};
                            return m;
                        }, {});
                        var shouldCreateNewObject = false;
                        for (var j = 0; j < datumFields.length; j++) {
                            if (objectFields[datumFields[j]]) {
                                shouldCreateNewObject = true;
                                break;
                            }
                        }
                        if (shouldCreateNewObject) {
                            this.objects[i] = self.model._new();
                        }
                    }
                } else {
                    this.objects[i] = null;
                }
            }
        }
        util.async.parallel([
                function (callback) {
                    var localIdentifiers = _.pluck(_.pluck(localLookups, 'datum'), '_id');
                    if (localIdentifiers.length) {
                        Store.getMultipleLocal(localIdentifiers, function (err, objects) {
                            if (!err) {
                                for (var i = 0; i < localIdentifiers.length; i++) {
                                    var obj = objects[i];
                                    var _id = localIdentifiers[i];
                                    var lookup = localLookups[i];
                                    if (!obj) {
                                        // If there are multiple mapping operations going on, there may be
                                        obj = cache.get({_id: _id});
                                        if (!obj)
                                            obj = self.model._new({_id: _id}, !self.disableNotifications);
                                        self.objects[lookup.index] = obj;
                                    } else {
                                        self.objects[lookup.index] = obj;
                                    }
                                }
                            }
                            callback(err);
                        });
                    } else {
                        callback();
                    }
                },
                function (callback) {
                    var remoteIdentifiers = _.pluck(_.pluck(remoteLookups, 'datum'), self.model.id);
                    if (remoteIdentifiers.length) {
                        if (Logger.trace.isEnabled)
                            Logger.trace('Looking up remoteIdentifiers: ' + util.prettyPrint(remoteIdentifiers));
                        Store.getMultipleRemote(remoteIdentifiers, self.model, function (err, objects) {
                            if (!err) {
                                if (Logger.trace.isEnabled) {
                                    var results = {};
                                    for (i = 0; i < objects.length; i++) {
                                        results[remoteIdentifiers[i]] = objects[i] ? objects[i]._id : null;
                                    }
                                    Logger.trace('Results for remoteIdentifiers: ' + util.prettyPrint(results));
                                }
                                for (i = 0; i < objects.length; i++) {
                                    var obj = objects[i];
                                    var lookup = remoteLookups[i];
                                    if (obj) {
                                        self.objects[lookup.index] = obj;
                                    } else {
                                        var data = {};
                                        var remoteId = remoteIdentifiers[i];
                                        data[self.model.id] = remoteId;
                                        var cacheQuery = {
                                            model: self.model
                                        };
                                        cacheQuery[self.model.id] = remoteId;
                                        var cached = cache.get(cacheQuery);
                                        if (cached) {
                                            self.objects[lookup.index] = cached;
                                        } else {
                                            self.objects[lookup.index] = self.model._new();
                                            // It's important that we map the remote identifier here to ensure that it ends
                                            // up in the cache.
                                            self.objects[lookup.index][self.model.id] = remoteId;
                                        }
                                    }
                                }
                            }
                            callback(err);
                        });
                    } else {
                        callback();
                    }
                }
            ],
            callback);
        return deferred ? deferred.promise : null;
    },
    _lookupSingleton: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        var self = this;
        this.model.get(function (err, singleton) {
            // Pick a random _id from the array of data being mapped onto the singleton object. Note that they should
            // always be the same. This is just a precaution.
            var _ids = _.pluck(self.data, '_id'),
                _id;
            for (i = 0; i < _ids.length; i++) {
                if (_ids[i]) {
                    _id = {_id: _ids[i]};
                    break;
                }
            }
            if (!singleton) singleton = self.model._new(_id);
            if (!err) {
                for (var i = 0; i < self.data.length; i++) {
                    self.objects[i] = singleton;
                }
            }
            callback(err);
        });
        return deferred ? deferred.promise : null;
    },
    _start: function (done) {
        if (this.data.length) {
            var self = this;
            var tasks = [];
            var lookupFunc = this.model.singleton ? this._lookupSingleton : this._lookup;
            tasks.push(_.bind(lookupFunc, this));
            tasks.push(_.bind(this._executeSubOperations, this));
            util.async.parallel(tasks, function () {
                self._map();
                done(self.errors.length ? self.errors : null, self.objects);
            });
        } else {
            done(null, []);
        }
    },
    getRelatedData: function (name) {
        var indexes = [];
        var relatedData = [];
        for (var i = 0; i < this.data.length; i++) {
            var datum = this.data[i];
            if (datum) {
                if (datum[name]) {
                    indexes.push(i);
                    relatedData.push(datum[name]);
                }
            }
        }
        return {
            indexes: indexes,
            relatedData: relatedData
        };
    },
    _constructSubOperation: function (name) {
        var relationships = this.model.relationships;
        var relationship = relationships[name];
        var reverseModel = relationship.forwardName == name ? relationship.reverseModel : relationship.forwardModel;
        var __ret = this.getRelatedData(name);
        var indexes = __ret.indexes;
        var relatedData = __ret.relatedData;
        if (relatedData.length) {
            var flatRelatedData = util.flattenArray(relatedData);
            var op = new MappingOperation({
                model: reverseModel,
                data: flatRelatedData,
                disableNotifications: this.disableNotifications
            });
            op.__relationshipName = name;
            op.__indexes = indexes;
        }
        return op;
    },
    gatherErrorsFromTasks: function () {
        var self = this;
        var relationshipNames = _.keys(this.subOps);
        _.each(relationshipNames, function (name) {
            var task = self.subOps[name].task;
            var indexes = self.subOps[name].__indexes;
            var errors = task.errors;
            if (errors.length) {
                var relatedData = self.getRelatedData(name).relatedData;
                var unflattenedErrors = util.unflattenArray(errors, relatedData);
                for (var i = 0; i < unflattenedErrors.length; i++) {
                    var idx = indexes[i];
                    var err = unflattenedErrors[i];
                    var isError = err;
                    if (util.isArray(err)) isError = _.reduce(err, function (memo, x) {
                        return memo || x
                    }, false);
                    if (isError) {
                        if (!self.errors[idx]) self.errors[idx] = {};
                        self.errors[idx][name] = err;
                    }
                }
            }
        });
    },
    _executeSubOperations: function (callback) {
        var self = this;
        var relationshipNames = _.keys(this.model.relationships);
        if (relationshipNames.length) {
            var tasks = _.reduce(relationshipNames, function (m, k) {
                var op = self._constructSubOperation(k);
                if (op) {
                    self.subOps[k] = op;
                    var task;
                    task = function (done) {
                        op.onCompletion(function () {
                            task.errors = op.errors;
                            done();
                        });
                        op.start();
                    };
                    self.subOps[k].task = task;
                    m.push(task);
                }
                return m;
            }, []);
            async.parallel(tasks, function () {
                self.gatherErrorsFromTasks(relationshipNames);
                callback();
            });
        } else {
            callback();
        }
    }
});

module.exports = MappingOperation;

<commit_msg>mapping op refactoring<commit_after>/**
 * @module mapping
 */

var Store = require('./store'),
    SiestaModel = require('./modelInstance'),
    log = require('./operation/log'),
    Operation = require('./operation/operation').Operation,
    InternalSiestaError = require('./error').InternalSiestaError,
    Query = require('./query'),
    cache = require('./cache'),
    util = require('./util'),
    _ = util._,
    async = util.async,
    ChangeType = require('./changes').ChangeType;

var Logger = log.loggerWithName('MappingOperation');

function SiestaError(opts) {
    this.opts = opts;
}
SiestaError.prototype.toString = function () {
    return JSON.stringify(this.opts, null, 4);
};

/**
 * Defines an encapsulated mapping operation where opts takes a mappin
 * @param {Object} opts
 */
function MappingOperation(opts) {
    Operation.call(this);

    this._opts = opts;

    util.extendFromOpts(this, opts, {
        model: null,
        data: null,
        objects: [],
        disableNotifications: false
    });

    //
    ///**
    // * @name mapping
    // * @type {Model}
    // */
    //defineSubProperty.call(this, 'model', this._opts);
    //
    ///**
    // * @name data
    // * @type {Array}
    // */
    //defineSubProperty.call(this, 'data', this._opts);
    //
    ///**
    // * @name objects
    // * @type {Array}
    // */
    //defineSubProperty.call(this, 'objects', this._opts);
    //
    ///**
    // * @name disableNotifications
    // * @type {bool}
    // */
    //defineSubProperty.call(this, 'disableNotifications', this._opts);

    //if (!this.objects) this.objects = [];

    /**
     * Array of errors where indexes map onto same index as the datum that caused an error.
     * @type {Array}
     */
    this.errors = [];

    this.name = 'Model Operation';
    this.work = _.bind(this._start, this);
    this.subOps = {};
}

MappingOperation.prototype = Object.create(Operation.prototype);

_.extend(MappingOperation.prototype, {
    mapAttributes: function () {
        for (var i = 0; i < this.data.length; i++) {
            var datum = this.data[i];
            var object = this.objects[i];
            // No point mapping object onto itself. This happens if a ModelInstance is passed as a relationship.
            if (datum != object) {
                if (object) { // If object is falsy, then there was an error looking up that object/creating it.
                    var fields = this.model._attributeNames;
                    _.each(fields, function (f) {
                        if (datum[f] !== undefined) { // null is fine
                            // If notifications are disabled we update __values object directly. This avoids triggering
                            // notifications which are built into the set function of the property.
                            if (this.disableNotifications) {
                                object.__values[f] = datum[f];
                            }
                            else {
                                object[f] = datum[f];
                            }
                        }
                    }.bind(this));
                    // PouchDB revision (if using storage module).
                    // TODO: Can this be pulled out of core?
                    if (datum._rev) object._rev = datum._rev;
                }
            }
        }
    },
    _map: function () {
        var self = this;
        var err;
        this.mapAttributes();
        var relationshipFields = _.keys(self.subOps);
        _.each(relationshipFields, function (f) {
            var op = self.subOps[f];
            var indexes = op.__indexes;
            var relatedData = self.getRelatedData(f).relatedData;
            var unflattenedObjects = util.unflattenArray(op.objects, relatedData);
            for (var i = 0; i < unflattenedObjects.length; i++) {
                var idx = indexes[i];
                // Errors are plucked from the suboperations.
                var error = self.errors[idx];
                err = error ? error[f] : null;
                if (!err) {
                    var related = unflattenedObjects[i]; // Can be array or scalar.
                    var object = self.objects[idx];
                    if (object) {
                        err = object.__proxies[f].set(related, {disableNotifications: self.disableNotifications});
                        if (err) {
                            if (!self.errors[idx]) self.errors[idx] = {};
                            self.errors[idx][f] = err;
                        }
                    }
                }
            }
        });
    },
    /**
     * For indices where no object is present, perform lookups, creating a new object if necessary.
     * @private
     */
    _lookup: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        var self = this;
        var remoteLookups = [];
        var localLookups = [];
        for (var i = 0; i < this.data.length; i++) {
            if (!this.objects[i]) {
                var lookup;
                var datum = this.data[i];
                var isScalar = typeof datum == 'string' || typeof datum == 'number' || datum instanceof String;
                if (datum) {
                    if (isScalar) {
                        lookup = {
                            index: i,
                            datum: {}
                        };
                        lookup.datum[self.model.id] = datum;
                        remoteLookups.push(lookup);
                    } else if (datum instanceof SiestaModel) { // We won't need to perform any mapping.
                        this.objects[i] = datum;
                    } else if (datum._id) {
                        localLookups.push({
                            index: i,
                            datum: datum
                        });
                    } else if (datum[self.model.id]) {
                        remoteLookups.push({
                            index: i,
                            datum: datum
                        });
                    } else {
                        // Create a new object if and only if the data has any fields that will actually
                        var datumFields = Object.keys(datum);
                        var objectFields = _.reduce(Object.keys(self.model.relationships).concat(self.model._attributeNames), function (m, x) {
                            m[x] = {};
                            return m;
                        }, {});
                        var shouldCreateNewObject = false;
                        for (var j = 0; j < datumFields.length; j++) {
                            if (objectFields[datumFields[j]]) {
                                shouldCreateNewObject = true;
                                break;
                            }
                        }
                        if (shouldCreateNewObject) {
                            this.objects[i] = self.model._new();
                        }
                    }
                } else {
                    this.objects[i] = null;
                }
            }
        }
        util.async.parallel([
                function (callback) {
                    var localIdentifiers = _.pluck(_.pluck(localLookups, 'datum'), '_id');
                    if (localIdentifiers.length) {
                        Store.getMultipleLocal(localIdentifiers, function (err, objects) {
                            if (!err) {
                                for (var i = 0; i < localIdentifiers.length; i++) {
                                    var obj = objects[i];
                                    var _id = localIdentifiers[i];
                                    var lookup = localLookups[i];
                                    if (!obj) {
                                        // If there are multiple mapping operations going on, there may be
                                        obj = cache.get({_id: _id});
                                        if (!obj)
                                            obj = self.model._new({_id: _id}, !self.disableNotifications);
                                        self.objects[lookup.index] = obj;
                                    } else {
                                        self.objects[lookup.index] = obj;
                                    }
                                }
                            }
                            callback(err);
                        });
                    } else {
                        callback();
                    }
                },
                function (callback) {
                    var remoteIdentifiers = _.pluck(_.pluck(remoteLookups, 'datum'), self.model.id);
                    if (remoteIdentifiers.length) {
                        if (Logger.trace.isEnabled)
                            Logger.trace('Looking up remoteIdentifiers: ' + util.prettyPrint(remoteIdentifiers));
                        Store.getMultipleRemote(remoteIdentifiers, self.model, function (err, objects) {
                            if (!err) {
                                if (Logger.trace.isEnabled) {
                                    var results = {};
                                    for (i = 0; i < objects.length; i++) {
                                        results[remoteIdentifiers[i]] = objects[i] ? objects[i]._id : null;
                                    }
                                    Logger.trace('Results for remoteIdentifiers: ' + util.prettyPrint(results));
                                }
                                for (i = 0; i < objects.length; i++) {
                                    var obj = objects[i];
                                    var lookup = remoteLookups[i];
                                    if (obj) {
                                        self.objects[lookup.index] = obj;
                                    } else {
                                        var data = {};
                                        var remoteId = remoteIdentifiers[i];
                                        data[self.model.id] = remoteId;
                                        var cacheQuery = {
                                            model: self.model
                                        };
                                        cacheQuery[self.model.id] = remoteId;
                                        var cached = cache.get(cacheQuery);
                                        if (cached) {
                                            self.objects[lookup.index] = cached;
                                        } else {
                                            self.objects[lookup.index] = self.model._new();
                                            // It's important that we map the remote identifier here to ensure that it ends
                                            // up in the cache.
                                            self.objects[lookup.index][self.model.id] = remoteId;
                                        }
                                    }
                                }
                            }
                            callback(err);
                        });
                    } else {
                        callback();
                    }
                }
            ],
            callback);
        return deferred ? deferred.promise : null;
    },
    _lookupSingleton: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        var self = this;
        this.model.get(function (err, singleton) {
            // Pick a random _id from the array of data being mapped onto the singleton object. Note that they should
            // always be the same. This is just a precaution.
            var _ids = _.pluck(self.data, '_id'),
                _id;
            for (i = 0; i < _ids.length; i++) {
                if (_ids[i]) {
                    _id = {_id: _ids[i]};
                    break;
                }
            }
            if (!singleton) singleton = self.model._new(_id);
            if (!err) {
                for (var i = 0; i < self.data.length; i++) {
                    self.objects[i] = singleton;
                }
            }
            callback(err);
        });
        return deferred ? deferred.promise : null;
    },
    _start: function (done) {
        if (this.data.length) {
            var self = this;
            var tasks = [];
            var lookupFunc = this.model.singleton ? this._lookupSingleton : this._lookup;
            tasks.push(_.bind(lookupFunc, this));
            tasks.push(_.bind(this._executeSubOperations, this));
            util.async.parallel(tasks, function () {
                self._map();
                done(self.errors.length ? self.errors : null, self.objects);
            });
        } else {
            done(null, []);
        }
    },
    getRelatedData: function (name) {
        var indexes = [];
        var relatedData = [];
        for (var i = 0; i < this.data.length; i++) {
            var datum = this.data[i];
            if (datum) {
                if (datum[name]) {
                    indexes.push(i);
                    relatedData.push(datum[name]);
                }
            }
        }
        return {
            indexes: indexes,
            relatedData: relatedData
        };
    },
    _constructSubOperation: function (name) {
        var relationship = this.model.relationships[name],
            reverseModel = relationship.forwardName == name ? relationship.reverseModel : relationship.forwardModel,
            __ret = this.getRelatedData(name),
            indexes = __ret.indexes,
            relatedData = __ret.relatedData;
        if (relatedData.length) {
            var flatRelatedData = util.flattenArray(relatedData);
            var op = new MappingOperation({
                model: reverseModel,
                data: flatRelatedData,
                disableNotifications: this.disableNotifications
            });
            op.__relationshipName = name;
            op.__indexes = indexes;
        }
        return op;
    },
    processErrorsFromTask: function (task) {
        var indexes = task.__indexes,
            name = task.__relationshipName;
        var errors = task.errors;
        if (errors.length) {
            var relatedData = this.getRelatedData(name).relatedData;
            var unflattenedErrors = util.unflattenArray(errors, relatedData);
            for (var i = 0; i < unflattenedErrors.length; i++) {
                var idx = indexes[i];
                var err = unflattenedErrors[i];
                var isError = err;
                if (util.isArray(err)) isError = _.reduce(err, function (memo, x) {
                    return memo || x
                }, false);
                if (isError) {
                    if (!this.errors[idx]) this.errors[idx] = {};
                    this.errors[idx][name] = err;
                }
            }
        }
    },
    _executeSubOperations: function (callback) {
        var self = this;
        var relationshipNames = _.keys(this.model.relationships);
        if (relationshipNames.length) {
            var tasks = _.reduce(relationshipNames, function (m, relationshipName) {
                var op = self._constructSubOperation(relationshipName);
                if (op) {
                    self.subOps[relationshipName] = op;
                    var task;
                    task = function (done) {
                        op.onCompletion(function () {
                            task.errors = op.errors;
                            self.processErrorsFromTask(task);
                            done();
                        });
                        op.start();
                    };
                    task.__indexes = op.__indexes;
                    task.__relationshipName = op.__relationshipName;
                    self.subOps[relationshipName].task = task;
                    m.push(task);
                }
                return m;
            }, []);
            async.parallel(tasks, function () {
                callback();
            });
        } else {
            callback();
        }
    }
});

module.exports = MappingOperation;

<|endoftext|>


<commit_before>var CorePlugin = require('./../corePlugin').CorePlugin,
  resources = require('./../../resources/model'),
  util = require('util'),
  utils = require('./../../utils/utils.js');

var modelTemperature, modelHumidity;

var Dht22Plugin = exports.Dht22Plugin = function (params) {
  CorePlugin.call(this, params, 'temperature', stop, simulate, null, null);
  modelTemperature = this.model;
  modelHumidity = utils.findProperty('humidity');

  // init
  addData([0, 0]);
};
util.inherits(Dht22Plugin, CorePlugin);

function stop() {
  actuator.unexport();
}

function simulate() {
  addData([utils.randomInt(0, 40), utils.randomInt(20, 100)]);
}

function addData(value) {
  modelTemperature.data.push({"t": value[0], "timestamp": utils.isoTimestamp()});
  modelHumidity.data.push({"h": value[1], "timestamp": utils.isoTimestamp()});
}

function showValue() {
  console.info('Temperature: %s C, humidity %s \%',
    modelTemperature.value, modelHumidity.value);
}

Dht22Plugin.prototype.connectHardware = function () {
  self = this;
  var sensorDriver = require('node-dht-sensor');
  var sensor = {
    initialize: function () {
      return sensorDriver.initialize(22, self.model.values.t.customFields.gpio);
    },
    read: function () {
      var readout = sensorDriver.read();
      addData([parseFloat(readout.temperature.toFixed(2)), parseFloat(readout.humidity.toFixed(2))]);
      showValue();

      setTimeout(function () {
        sensor.read();
      }, 2000);
    }
  };
  if (sensor.initialize()) {
    console.info('Hardware %s sensor started!', self.model.name);
    sensor.read();
  } else {
    console.warn('Failed to initialize sensor!');
  }
};

<commit_msg>debugging GPIO<commit_after>var CorePlugin = require('./../corePlugin').CorePlugin,
  resources = require('./../../resources/model'),
  util = require('util'),
  utils = require('./../../utils/utils.js');

var modelTemperature, modelHumidity;

var Dht22Plugin = exports.Dht22Plugin = function (params) {
  CorePlugin.call(this, params, 'temperature', stop, simulate, null, null);
  modelTemperature = this.model;
  modelHumidity = utils.findProperty('humidity');

  // init
  addData([0, 0]);
};
util.inherits(Dht22Plugin, CorePlugin);

function stop() {
  actuator.unexport();
}

function simulate() {
  addData([utils.randomInt(0, 40), utils.randomInt(20, 100)]);
}

function addData(value) {
  modelTemperature.data.push({"t": value[0], "timestamp": utils.isoTimestamp()});
  modelHumidity.data.push({"h": value[1], "timestamp": utils.isoTimestamp()});
}

function showValue() {
  console.info('Temperature: %s C, humidity %s \%',
    modelTemperature.value, modelHumidity.value);
}

Dht22Plugin.prototype.connectHardware = function () {
  self = this;
  var sensorDriver = require('node-dht-sensor');
  var sensor = {
    initialize: function () {
      Console.log('GPIO, %s', self.model.values.t.customFields.gpio);
      return sensorDriver.initialize(22, self.model.values.t.customFields.gpio);
    },
    read: function () {
      var readout = sensorDriver.read();
      addData([parseFloat(readout.temperature.toFixed(2)), parseFloat(readout.humidity.toFixed(2))]);
      showValue();

      setTimeout(function () {
        sensor.read();
      }, 2000);
    }
  };
  if (sensor.initialize()) {
    console.info('Hardware %s sensor started!', self.model.name);
    sensor.read();
  } else {
    console.warn('Failed to initialize sensor!');
  }
};

<|endoftext|>