jhpoelen/eol-globi-js

View on GitHub
index.js

Summary

Maintainability
F
3 days
Test Coverage
var globiData = require('globi-data');
var EventEmitter = require('events').EventEmitter;
var jQuery = require('jquery');
var taxaprisma = require('taxaprisma');

var globi = {};
globi.globiData = globiData;
globi.jQuery = jQuery;

/**
 * Default area for all kinds of location consuming functions
 *
 * @type {{north: number, west: number, south: number, east: number}}
 */
globi.DEFAULT_AREA = {
    north: 90,
    west: -180,
    south: -90,
    east: 180
};

globi.createTaxonInfo = function (scientificName) {
    var ee = new EventEmitter();
    var taxonInfoDiv = document.createElement('div');
    taxonInfoDiv.setAttribute('class', 'globi-taxon-info');
    var callback = function (taxonInfo) {
        var div = document.createElement('div');
        div.className = 'result-source';
        div.innerHTML = globi.getTaxonTemplate(taxonInfo);
        taxonInfoDiv.appendChild(div);
        ee.emit('ready');
    };
    globiData.findTaxonInfo(scientificName, callback);

    ee.appendTaxonInfoTo = function (target) {
        target.appendChild(taxonInfoDiv);
    };

    ee.registerOnClick = function (onClick) {
        var img = taxonInfoDiv.getElementsByTagName('img')[0];
        if (img) {
            img.addEventListener('click', function () {
                onClick(scientificName);
            });
        }
    };
    return ee;
};

/**
 * Callback list handling (incl. trigger control)
 *
 * inspired by jQuery#Callbacks
 *
 * @param {String|Object} options
 * @returns {Object}
 * @constructor
 */
globi.Callbacks = function (options) {
    function createOptions(options) {
        var object = {}, names = options.split(' ');
        for (var i = 0, name; name = names[i]; i++) {
            object[name] = true;
        }
        return object;
    }

    options = typeof options === 'string' ?
        createOptions(options) :
        globi.extend({}, options);

    var firing,
        memory,
        fired,
        locked,
        list = [],
        queue = [],
        firingIndex = -1,
        fire = function () {
            locked = options.once;

            fired = firing = true;
            for (; queue.length; firingIndex = -1) {
                memory = queue.shift();
                while (++firingIndex < list.length) {
                    if (list[firingIndex].apply(memory[0], memory[1]) === false &&
                        options.stopOnFalse) {
                        firingIndex = list.length;
                        memory = false;
                    }
                }
            }

            if (!options.memory) {
                memory = false;
            }

            firing = false;

            if (locked) {
                if (memory) {
                    list = [];
                } else {
                    list = '';
                }
            }
        },

        self = {
            add: function () {
                if (list) {
                    if (memory && !firing) {
                        firingIndex = list.length - 1;
                        queue.push(memory);
                    }

                    (function add(args) {
                        for (var i = 0, arg; arg = args[i]; i++) {
                            if (typeof arg === 'function') {
                                if (!options.unique || !self.has(arg)) {
                                    list.push(arg);
                                }
                            } else if (arg && arg.length && typeof arg !== 'string') {
                                add(arg);
                            }
                        }
                    })(arguments);

                    if (memory && !firing) {
                        fire();
                    }
                }
                return this;
            },

            remove: function () {
                for (var i = 0, arg; arg = arguments[i]; i++) {
                    var index;
                    while ((index = list.indexOf(arg)) > -1) {
                        list.splice(index, 1);

                        if (index <= firingIndex) {
                            firingIndex--;
                        }
                    }
                }
                return this;
            },

            has: function (fn) {
                return fn ?
                    list.indexOf(fn) > -1 :
                    list.length > 0;
            },

            empty: function () {
                if (list) {
                    list = [];
                }
                return this;
            },

            disable: function () {
                locked = queue = [];
                list = memory = '';
                return this;
            },

            disabled: function () {
                return !list;
            },

            lock: function () {
                locked = queue = [];
                if (!memory && !firing) {
                    list = memory = '';
                }
                return this;
            },

            locked: function () {
                return !!locked;
            },

            fireWith: function (context, args) {
                if (!locked) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    queue.push(args);
                    if (!firing) {
                        fire();
                    }
                }
                return this;
            },

            fire: function () {
                self.fireWith(this, arguments);
                return this;
            },

            fired: function () {
                return !!fired;
            }
        };

    return self;
};

/**
 * Deferred function handling
 * allows easier asynchronous call management
 *
 * heavily inspired by jQuery#Deferred
 *
 * @param {Function} [func]
 * @returns {Object}
 * @constructor
 */
globi.Deferred = function (func) {
    var tuples = [
            [ 'resolve', 'done', globi.Callbacks('once memory'), 'resolved' ],
            [ 'reject', 'fail', globi.Callbacks('once memory'), 'rejected' ],
            [ 'notify', 'progress', globi.Callbacks('memory') ]
        ],
        state = 'pending',
        promise = {
            state: function () {
                return state;
            },
            always: function () {
                deferred.done(arguments).fail(arguments);
                return this;
            },
            then: function () {
                var fns = arguments;
                return globi.Deferred(function (newDefer) {
                    tuples.forEach(function (tuple, i) {
                        var fn = typeof fns[i] === 'function' && fns[i];
                        deferred[tuple[1]](function () {
                            var returned = fn && fn.apply(this, arguments);
                            if (returned && typeof returned.promise === 'function') {
                                returned.promise()
                                    .done(newDefer.resolve)
                                    .fail(newDefer.reject)
                                    .progress(newDefer.notify);
                            } else {
                                newDefer[tuple[0] + 'With'](
                                    this === promise ? newDefer.promise() : this,
                                    fn ? [ returned ] : arguments
                                );
                            }
                        });
                    });
                    fns = null;
                }).promise();
            },
            promise: function (obj) {
                return obj != null ? globi.extend(obj, promise) : promise;
            }
        },
        deferred = {};

    tuples.forEach(function (tuple, i) {
        var list = tuple[2],
            stateString = tuple[3];

        promise[tuple[1]] = list.add;

        if (stateString) {
            list.add(function () {
                state = stateString;
            }, tuples[i ^ 1][2].disable, tuples[2][2].lock);
        }

        deferred[tuple[0]] = function () {
            deferred[tuple[0] + "With"](this === deferred ? promise : this, arguments);
            return this;
        };
        deferred[tuple[0] + "With"] = list.fireWith;
    });

    promise.promise(deferred);

    if (func) {
        func.call(deferred, deferred);
    }

    return deferred;
};

/**
 * Simple object extension
 *
 * @param {Object} target
 * @param {Object} source
 * @returns {Object}
 */
globi.extend = function (target, source) {
    for (var k in source) {
        if (source.hasOwnProperty(k)) {
            var value = source[k];
            if (target.hasOwnProperty(k) && typeof target[k] === "object" && typeof value === "object") {
                globi.extend(target[k], value);
            } else {
                target[k] = value;
            }
        }
    }
    return target;
};

/**
 * Fetches paginated data by retrieving it chunk by chunk
 *
 * @param {Object} settings
 * @constructor
 */
globi.PaginatedDataFetcher = function (settings) {
    this.settings = globi.extend({
        offset: 0,
        limit: 1024,
        url: '',
        maximumRequestCount: 2,
        requestCount: 0
    }, settings);
    this._initialOffset = this.settings.offset;
    this.init();
};

globi.extend(globi.PaginatedDataFetcher.prototype, {
    init: function () {
        this._data = [];
        this._columns = [];
    },

    setUrl: function (url) {
        this.url = url;
    },

    fetchChunk: function (offset) {
        var me = this, settings = me.settings;
        var d = globi.Deferred();
        jQuery.ajax(settings.url + '&limit=' + settings.limit + '&offset=' + offset, {
            dataType: 'json'
        }).done(function (response) {
            d.resolve(response);
        });
        return d.promise();
    },

    poll: function () {
        var me = this, settings = me.settings;
        return me.fetchChunk(settings.offset).then(function (reponse) {
            me._data = me._data.concat(reponse.data);
            me._columns = reponse.columns;
            if (reponse.data.length < settings.limit) {
                return me._data;
            }

            settings.offset = settings.offset + settings.limit;

            settings.requestCount = settings.requestCount + 1;
            if (settings.maximumRequestCount <= settings.requestCount) {
                return me._data;
            }
            return me.poll();
        });
    },

    fetch: function (callback, reset) {
        var me = this;
        reset = reset ? !!reset : true;
        if (reset) {
            me.settings.offset = me._initialOffset;
        }
        me.init();
        me.poll().then(function () {
            return true;
        }).done(function () {
            callback({ columns: me._columns, data: me._data });
        });
    }
});

globi.ResponseMapper = function () {
    var rawData = arguments.length === 1 ? arguments[0] : {},
        isArray = Array.isArray(rawData),
        rowNames = rawData['columns'] ? rawData['columns'] : [],
        data = rawData['data'] ? rawData['data'] : [],
        dataLength = isArray ? rawData.length : data.length;

    return function () {
        if (arguments.length === 0) {
            if (isArray) {
                return rawData;
            }
            return data.map(function (item) {
                return RowMapper(item, rowNames)
            });
        }
        if (arguments.length === 1 && typeof arguments[0] === 'number' && arguments[0] < dataLength) {
            if (isArray) {
                return rawData[arguments[0]];
            }
            return RowMapper(data[arguments[0]], rowNames);
        }
    };

    function RowMapper(data, names) {
        var row = {}, value;
        for (var i = 0, name; name = names[i]; i++) {
            value = data[i] ? data[i] : null;
            row[name] = value;
        }
        return row;
    }
};

globi.getTaxonTemplate = function(taxon) {
    var commonName = taxon['commonName'] ? taxon['commonName'] : '',
        scientificName = taxon['scientificName'] ? taxon['scientificName'] : '',
        path = taxon['taxonPath'] ? taxon['taxonPath'] : '',
        thumbnailURL = taxon['thumbnailURL'] ? taxon['thumbnailURL'] : '../img/no-image-available.png',
        infoURL = taxon['infoURL'] ? taxon['infoURL'] : '';

    return [
        '<div class="source-data">',
        '<div class="scientific-name" style="color: ' + taxaprisma.colorFor(path) + ';">' + scientificName + '</div>',
        '<a target="_blank" href="' + infoURL + '">',
        '<div class="taxon-image">',
        '<table style="width: 100%;"><tbody style="background-color: transparent;"><tr>',
        '<td style="width: 50%;vertical-align: middle;"><img height="50px" src="' + thumbnailURL + '" /></td>',
        '<td style="width: 50%;vertical-align: middle; text-align: right;"><img height="35px" src="' + taxaprisma.imageDataUrlFor(path) + '" /></td>',
        '</tr></tbody></table>',
        '</div>',
        '</a>',
        '<div class="common-name">' + commonName + '</div>',
        '</div>'
    ].join('');
};

module.exports = globi;