UNPKG

flyd

Version:

The less is more, modular, functional reactive programming library

779 lines (679 loc) 20.2 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); // AMD. Register as an anonymous module. } else if (typeof exports === 'object') { module.exports = factory(); // NodeJS } else { // Browser globals (root is window) root.flyd = factory(); } }(this, function () { 'use strict'; function isFunction(obj) { return !!(obj && obj.constructor && obj.call && obj.apply); } function notUndef(v) { return v !== undefined; } var toUpdate = []; var inStream; function map(f, s) { return stream([s], function(self) { self(f(s())); }); } function boundMap(f) { return map(f, this); } var scan = curryN(3, function(f, acc, s) { var ns = stream([s], function() { return (acc = f(acc, s())); }); if (!ns.hasVal) ns(acc); return ns; }); var merge = curryN(2, function(s1, s2) { var s = immediate(stream([s1, s2], function(n, changed) { return changed[0] ? changed[0]() : s1.hasVal ? s1() : s2(); })); endsOn(stream([s1.end, s2.end], function(self, changed) { return true; }), s); return s; }); function ap(s2) { var s1 = this; return stream([s1, s2], function() { return s1()(s2()); }); } function initialDepsNotMet(stream) { stream.depsMet = stream.deps.every(function(s) { return s.hasVal; }); return !stream.depsMet; } function updateStream(s) { if ((s.depsMet !== true && initialDepsNotMet(s)) || (s.end !== undefined && s.end.val === true)) return; inStream = s; var returnVal = s.fn(s, s.depsChanged); if (returnVal !== undefined) { s(returnVal); } inStream = undefined; while (s.depsChanged.length > 0) s.depsChanged.shift(); } var order = []; var orderNextIdx = -1; function findDeps(s) { var i, listeners = s.listeners; if (s.queued === false) { s.queued = true; for (i = 0; i < listeners.length; ++i) { findDeps(listeners[i]); } order[++orderNextIdx] = s; } } function updateDeps(s) { var i, list, listeners = s.listeners; for (i = 0; i < listeners.length; ++i) { list = listeners[i]; if (list.end === s) { endStream(list); } else { list.depsChanged.push(s); findDeps(list); } } for (i = orderNextIdx; i >= 0; --i) { if (order[i].depsChanged !== undefined && order[i].depsChanged.length > 0) { updateStream(order[i]); } order[i].queued = false; } orderNextIdx = -1; } function flushUpdate() { while (toUpdate.length > 0) updateDeps(toUpdate.shift()); } function isStream(stream) { return isFunction(stream) && 'hasVal' in stream; } function streamToString() { return 'stream(' + this.val + ')'; } function createStream() { function s(n) { var i, list; if (arguments.length === 0) { return s.val; } else { if (n !== undefined && n !== null && isFunction(n.then)) { n.then(s); return; } s.val = n; s.hasVal = true; if (inStream === undefined) { updateDeps(s); if (toUpdate.length !== 0) flushUpdate(); } else if (inStream === s) { for (i = 0; i < s.listeners.length; ++i) { list = s.listeners[i]; if (list.end !== s) list.depsChanged.push(s); else endStream(list); } } else { toUpdate.push(s); } return s; } } s.hasVal = false; s.val = undefined; s.listeners = []; s.queued = false; s.end = undefined; s.map = boundMap; s.ap = ap; s.of = stream; s.toString = streamToString; return s; } function createDependentStream(deps, fn) { var i, s = createStream(); s.fn = fn; s.deps = deps; s.depsMet = false; s.depsChanged = []; for (i = 0; i < deps.length; ++i) { deps[i].listeners.push(s); } return s; } function immediate(s) { if (s.depsMet === false) { s.depsMet = true; updateStream(s); if (toUpdate.length !== 0) flushUpdate(); } return s; } function removeListener(s, listeners) { var idx = listeners.indexOf(s); listeners[idx] = listeners[listeners.length - 1]; listeners.length--; } function detachDeps(s) { for (var i = 0; i < s.deps.length; ++i) { removeListener(s, s.deps[i].listeners); } s.deps.length = 0; } function endStream(s) { if (s.deps !== undefined) detachDeps(s); if (s.end !== undefined) detachDeps(s.end); } function endsOn(endS, s) { detachDeps(s.end); endS.listeners.push(s.end); s.end.deps.push(endS); return s; } function stream(arg, fn) { var s, deps; var endStream = createDependentStream([], function() { return true; }); if (arguments.length > 1) { deps = arg.filter(notUndef); s = createDependentStream(deps, fn); s.end = endStream; endStream.listeners.push(s); var depEndStreams = deps.map(function(d) { return d.end; }).filter(notUndef); endsOn(createDependentStream(depEndStreams, function() { return true; }, true), s); updateStream(s); if (toUpdate.length !== 0) flushUpdate(); } else { s = createStream(); s.end = endStream; endStream.listeners.push(s); if (arguments.length === 1) s(arg); } return s; } var transduce = curryN(2, function(xform, source) { xform = xform(new StreamTransformer()); return stream([source], function(self) { var res = xform['@@transducer/step'](undefined, source()); if (res && res['@@transducer/reduced'] === true) { self.end(true); return res['@@transducer/value']; } else { return res; } }); }); function StreamTransformer() { } StreamTransformer.prototype['@@transducer/init'] = function() { }; StreamTransformer.prototype['@@transducer/result'] = function() { }; StreamTransformer.prototype['@@transducer/step'] = function(s, v) { return v; }; // Own curry implementation snatched from Ramda // Figure out something nicer later on var _ = {placeholder: true}; // Detect both own and Ramda placeholder function isPlaceholder(p) { return p === _ || (p && p.ramda === 'placeholder'); } function toArray(arg) { var arr = []; for (var i = 0; i < arg.length; ++i) { arr[i] = arg[i]; } return arr; } // Modified versions of arity and curryN from Ramda function ofArity(n, fn) { if (arguments.length === 1) { return ofArity.bind(undefined, n); } switch (n) { case 0: return function () { return fn.apply(this, arguments); }; case 1: return function (a0) { void a0; return fn.apply(this, arguments); }; case 2: return function (a0, a1) { void a1; return fn.apply(this, arguments); }; case 3: return function (a0, a1, a2) { void a2; return fn.apply(this, arguments); }; case 4: return function (a0, a1, a2, a3) { void a3; return fn.apply(this, arguments); }; case 5: return function (a0, a1, a2, a3, a4) { void a4; return fn.apply(this, arguments); }; case 6: return function (a0, a1, a2, a3, a4, a5) { void a5; return fn.apply(this, arguments); }; case 7: return function (a0, a1, a2, a3, a4, a5, a6) { void a6; return fn.apply(this, arguments); }; case 8: return function (a0, a1, a2, a3, a4, a5, a6, a7) { void a7; return fn.apply(this, arguments); }; case 9: return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) { void a8; return fn.apply(this, arguments); }; case 10: return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { void a9; return fn.apply(this, arguments); }; default: throw new Error('First argument to arity must be a non-negative integer no greater than ten'); } } function curryN(length, fn) { return ofArity(length, function () { var n = arguments.length; var shortfall = length - n; var idx = n; while (--idx >= 0) { if (isPlaceholder(arguments[idx])) { shortfall += 1; } } if (shortfall <= 0) { return fn.apply(this, arguments); } else { var initialArgs = toArray(arguments); return curryN(shortfall, function () { var currentArgs = toArray(arguments); var combinedArgs = []; var idx = -1; while (++idx < n) { var val = initialArgs[idx]; combinedArgs[idx] = isPlaceholder(val) ? currentArgs.shift() : val; } return fn.apply(this, combinedArgs.concat(currentArgs)); }); } }); } return { stream: stream, isStream: isStream, transduce: transduce, merge: merge, reduce: scan, // Legacy scan: scan, endsOn: endsOn, map: curryN(2, map), curryN: curryN, _: _, immediate: immediate, }; })); },{}],2:[function(require,module,exports){ (function() { 'use strict'; if (self.fetch) { return } function normalizeName(name) { if (typeof name !== 'string') { name = name.toString(); } if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { throw new TypeError('Invalid character in header field name') } return name.toLowerCase() } function normalizeValue(value) { if (typeof value !== 'string') { value = value.toString(); } return value } function Headers(headers) { this.map = {} var self = this if (headers instanceof Headers) { headers.forEach(function(name, values) { values.forEach(function(value) { self.append(name, value) }) }) } else if (headers) { Object.getOwnPropertyNames(headers).forEach(function(name) { self.append(name, headers[name]) }) } } Headers.prototype.append = function(name, value) { name = normalizeName(name) value = normalizeValue(value) var list = this.map[name] if (!list) { list = [] this.map[name] = list } list.push(value) } Headers.prototype['delete'] = function(name) { delete this.map[normalizeName(name)] } Headers.prototype.get = function(name) { var values = this.map[normalizeName(name)] return values ? values[0] : null } Headers.prototype.getAll = function(name) { return this.map[normalizeName(name)] || [] } Headers.prototype.has = function(name) { return this.map.hasOwnProperty(normalizeName(name)) } Headers.prototype.set = function(name, value) { this.map[normalizeName(name)] = [normalizeValue(value)] } // Instead of iterable for now. Headers.prototype.forEach = function(callback) { var self = this Object.getOwnPropertyNames(this.map).forEach(function(name) { callback(name, self.map[name]) }) } function consumed(body) { if (body.bodyUsed) { return Promise.reject(new TypeError('Already read')) } body.bodyUsed = true } function fileReaderReady(reader) { return new Promise(function(resolve, reject) { reader.onload = function() { resolve(reader.result) } reader.onerror = function() { reject(reader.error) } }) } function readBlobAsArrayBuffer(blob) { var reader = new FileReader() reader.readAsArrayBuffer(blob) return fileReaderReady(reader) } function readBlobAsText(blob) { var reader = new FileReader() reader.readAsText(blob) return fileReaderReady(reader) } var support = { blob: 'FileReader' in self && 'Blob' in self && (function() { try { new Blob(); return true } catch(e) { return false } })(), formData: 'FormData' in self } function Body() { this.bodyUsed = false this._initBody = function(body) { this._bodyInit = body if (typeof body === 'string') { this._bodyText = body } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { this._bodyBlob = body } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { this._bodyFormData = body } else if (!body) { this._bodyText = '' } else { throw new Error('unsupported BodyInit type') } } if (support.blob) { this.blob = function() { var rejected = consumed(this) if (rejected) { return rejected } if (this._bodyBlob) { return Promise.resolve(this._bodyBlob) } else if (this._bodyFormData) { throw new Error('could not read FormData body as blob') } else { return Promise.resolve(new Blob([this._bodyText])) } } this.arrayBuffer = function() { return this.blob().then(readBlobAsArrayBuffer) } this.text = function() { var rejected = consumed(this) if (rejected) { return rejected } if (this._bodyBlob) { return readBlobAsText(this._bodyBlob) } else if (this._bodyFormData) { throw new Error('could not read FormData body as text') } else { return Promise.resolve(this._bodyText) } } } else { this.text = function() { var rejected = consumed(this) return rejected ? rejected : Promise.resolve(this._bodyText) } } if (support.formData) { this.formData = function() { return this.text().then(decode) } } this.json = function() { return this.text().then(JSON.parse) } return this } // HTTP methods whose capitalization should be normalized var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] function normalizeMethod(method) { var upcased = method.toUpperCase() return (methods.indexOf(upcased) > -1) ? upcased : method } function Request(url, options) { options = options || {} this.url = url this.credentials = options.credentials || 'omit' this.headers = new Headers(options.headers) this.method = normalizeMethod(options.method || 'GET') this.mode = options.mode || null this.referrer = null if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { throw new TypeError('Body not allowed for GET or HEAD requests') } this._initBody(options.body) } function decode(body) { var form = new FormData() body.trim().split('&').forEach(function(bytes) { if (bytes) { var split = bytes.split('=') var name = split.shift().replace(/\+/g, ' ') var value = split.join('=').replace(/\+/g, ' ') form.append(decodeURIComponent(name), decodeURIComponent(value)) } }) return form } function headers(xhr) { var head = new Headers() var pairs = xhr.getAllResponseHeaders().trim().split('\n') pairs.forEach(function(header) { var split = header.trim().split(':') var key = split.shift().trim() var value = split.join(':').trim() head.append(key, value) }) return head } Body.call(Request.prototype) function Response(bodyInit, options) { if (!options) { options = {} } this._initBody(bodyInit) this.type = 'default' this.url = null this.status = options.status this.ok = this.status >= 200 && this.status < 300 this.statusText = options.statusText this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) this.url = options.url || '' } Body.call(Response.prototype) self.Headers = Headers; self.Request = Request; self.Response = Response; self.fetch = function(input, init) { // TODO: Request constructor should accept input, init var request if (Request.prototype.isPrototypeOf(input) && !init) { request = input } else { request = new Request(input, init) } return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest() function responseURL() { if ('responseURL' in xhr) { return xhr.responseURL } // Avoid security warnings on getResponseHeader when not allowed by CORS if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { return xhr.getResponseHeader('X-Request-URL') } return; } xhr.onload = function() { var status = (xhr.status === 1223) ? 204 : xhr.status if (status < 100 || status > 599) { reject(new TypeError('Network request failed')) return } var options = { status: status, statusText: xhr.statusText, headers: headers(xhr), url: responseURL() } var body = 'response' in xhr ? xhr.response : xhr.responseText; resolve(new Response(body, options)) } xhr.onerror = function() { reject(new TypeError('Network request failed')) } xhr.open(request.method, request.url, true) if (request.credentials === 'include') { xhr.withCredentials = true } if ('responseType' in xhr && support.blob) { xhr.responseType = 'blob' } request.headers.forEach(function(name, values) { values.forEach(function(value) { xhr.setRequestHeader(name, value) }) }) xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) }) } self.fetch.polyfill = true })(); },{}],3:[function(require,module,exports){ var flyd = require('flyd'); require('whatwg-fetch'); document.addEventListener('DOMContentLoaded', function() { var suggestionsContainerElm = document.querySelector('.suggestions'); var suggestionsElms = suggestionsContainerElm.children; function update(model, action) { // Take action & model, return updated model if (action.type === 'refresh') { return {loaded: [], suggested: []}; } else if (action.type === 'loaded') { return {loaded: action.data, suggested: action.data.slice(0, 3)}; } else if (action.type === 'remove') { return { loaded: model.loaded, suggested: model.suggested.map(function(sug, i) { return action.nr === i ? model.loaded[Math.floor(Math.random()*model.loaded.length)] : sug; }) }; } } function render(model) { // Take model, modify DOM if (model.suggested.length === 0) { suggestionsContainerElm.style.visibility = 'hidden'; } else { suggestionsContainerElm.style.visibility = 'visible'; model.suggested.forEach(function(user, i) { var suggestionElm = suggestionsElms[i]; var usernameElm = suggestionElm.querySelector('.username'); usernameElm.href = user.html_url; usernameElm.textContent = user.login; var imgEl = suggestionElm.querySelector('img'); imgEl.src = ''; imgEl.src = user.avatar_url; }); } } var initialState = { loaded: [], suggested: [], }; // Streams var action$ = flyd.stream(); var model$ = flyd.scan(update, initialState, action$); flyd.map(render, model$); function makeRequest() { var randomOffset = Math.floor(Math.random()*500); return fetch('https://api.github.com/users?since=' + randomOffset) .then(function(res) { return res.json(); }) .then(function(data) { action$({type: 'loaded', data: data}); }); } makeRequest(); function sendRemoveAction(idx) { action$({type: 'remove', nr: idx}); } document.getElementById('refresh').addEventListener('click', makeRequest); document.getElementById('remove0').addEventListener('click', sendRemoveAction.bind(null, 0)); document.getElementById('remove1').addEventListener('click', sendRemoveAction.bind(null, 1)); document.getElementById('remove2').addEventListener('click', sendRemoveAction.bind(null, 2)); }); },{"flyd":1,"whatwg-fetch":2}]},{},[3]);