<commit_before>var Promise = require('es6-promise').Promise

var logger = require('../lib/logger')
var config = require('../lib/config')
var transport = require('../lib/transport')
var utils = require('../lib/utils')
var context = require('./context')
var stackTrace = require('./stacktrace')
var promiseMapSeries = require('promise-map-series')

module.exports = {

  getFramesForCurrent: function () {
    return stackTrace.get().then(function (frames) {
      var framesPromises = promiseMapSeries(frames, function (stack) {
        return this.buildOpbeatFrame(stack)
      }.bind(this))

      return framesPromises.then(function (frames) {
        return frames
      })
    }.bind(this))
  },

  buildOpbeatFrame: function buildOpbeatFrame (stack) {
    return new Promise(function (resolve, reject) {
      if (!stack.fileName && !stack.lineNumber) {
        // Probably an stack from IE, return empty frame as we can't use it.
        return resolve({})
      }

      var filePath = this.cleanFilePath(stack.fileName)
      var fileName = this.filePathToFileName(filePath)

      if(this.isFileInline(filePath)) {
        fileName = '(inline script)'
      }

      // Build Opbeat frame data
      var frame = {
        'filename': fileName,
        'lineno': stack.lineNumber,
        'colno': stack.columnNumber,
        'function': stack.functionName || '[anonymous]',
        'abs_path': stack.fileName,
        'in_app': this.isFileInApp(filePath)
      }

      // Detect Sourcemaps
      var sourceMapResolver = context.getFileSourceMapUrl(filePath)

      sourceMapResolver.then(function (sourceMapUrl) {
        frame.sourcemap_url = sourceMapUrl
        resolve(frame)
      })['catch'](function () {
        // // Resolve contexts if no source map
        var filePath = this.cleanFilePath(stack.fileName)
        var contextsResolver = context.getExceptionContexts(filePath, stack.lineNumber)

        contextsResolver.then(function (contexts) {
          frame.pre_context = contexts.preContext
          frame.context_line = contexts.contextLine
          frame.post_context = contexts.postContext

          resolve(frame)
        })['catch'](function () {
          resolve(frame)
        })
      }.bind(this))
    }.bind(this))
  },

  stackInfoToOpbeatException: function (stackInfo) {
    return new Promise(function (resolve, reject) {
      if (stackInfo.stack && stackInfo.stack.length) {
        var framesPromises = promiseMapSeries(stackInfo.stack, function (stack, index) {
          return this.buildOpbeatFrame(stack)
        }.bind(this))

        framesPromises.then(function (frames) {
          stackInfo.frames = frames
          stackInfo.stack = null
          resolve(stackInfo)
        })
      } else {
        resolve(stackInfo)
      }
    }.bind(this))
  },

  processOpbeatException: function (exception) {
    var type = exception.type
    var message = String(exception.message) || 'Script error'
    var filePath = this.cleanFilePath(exception.fileurl)
    var fileName = this.filePathToFileName(filePath)
    var frames = exception.frames || []
    var culprit

    if (frames && frames.length) {
      // Opbeat.com expects frames oldest to newest and JS sends them as newest to oldest
      frames.reverse()
    } else if (fileName) {
      frames.push({
        filename: fileName,
        lineno: exception.lineno
      })
    }

    var stacktrace = {
      frames: frames
    }

    // Set fileName from last frame, if filename is missing
    if (!fileName && frames.length) {
      var lastFrame = frames[frames.length - 1]
      if (lastFrame.filename) {
        fileName = lastFrame.filename
      } else {
        // If filename empty, assume inline script
        fileName = '(inline script)'
      }
    }

    if(this.isFileInline(filePath)) {
      culprit = '(inline script)'
    } else {
      culprit = fileName
    }

    var data = {
      message: type + ': ' + message,
      culprit: culprit,
      exception: {
        type: type,
        value: message
      },
      http: {
        url: window.location.href
      },
      stacktrace: stacktrace,
      user: config.get('context.user'),
      level: null,
      logger: null,
      machine: null
    }

    data.extra = this.getBrowserSpecificMetadata()

    if (config.get('context.extra')) {
      data.extra = utils.mergeObject(data.extra, config.get('context.extra'))
    }

    logger.log('opbeat.exceptions.processOpbeatException', data)
    transport.sendError(data)
  },

  cleanFilePath: function (filePath) {
    if (!filePath) {
      filePath = ''
    }

    if (filePath === '<anonymous>') {
      filePath = ''
    }

    return filePath
  },

  filePathToFileName: function (fileUrl) {
    var origin = window.location.origin || window.location.protocol + '//' + window.location.hostname + (window.location.port ? (':' + window.location.port) : '')

    if (fileUrl.indexOf(origin) > -1) {
      fileUrl = fileUrl.replace(origin + '/', '')
    }

    return fileUrl
  },

  isFileInline: function (fileUrl) {
    if(fileUrl) {
      return window.location.href.indexOf(fileUrl) === 0
    } else {
      return false
    }
  },

  isFileInApp: function (filename) {
    var pattern = config.get('libraryPathPattern')
    return !RegExp(pattern).test(filename)
  },

  getBrowserSpecificMetadata: function () {
    var viewportInfo = utils.getViewPortInfo()
    var extra = {
      'environment': {
        'utcOffset': new Date().getTimezoneOffset() / -60.0,
        'browserWidth': viewportInfo.width,
        'browserHeight': viewportInfo.height,
        'screenWidth': window.screen.width,
        'screenHeight': window.screen.height,
        'language': navigator.language,
        'userAgent': navigator.userAgent,
        'platform': navigator.platform
      },
      'page': {
        'referer': document.referrer,
        'host': document.domain,
        'location': window.location.href
      }
    }

    return extra
  }

}
<commit_msg>Use <anonymous> consistently for anonymous functions<commit_after>var Promise = require('es6-promise').Promise

var logger = require('../lib/logger')
var config = require('../lib/config')
var transport = require('../lib/transport')
var utils = require('../lib/utils')
var context = require('./context')
var stackTrace = require('./stacktrace')
var promiseMapSeries = require('promise-map-series')

module.exports = {

  getFramesForCurrent: function () {
    return stackTrace.get().then(function (frames) {
      var framesPromises = promiseMapSeries(frames, function (stack) {
        return this.buildOpbeatFrame(stack)
      }.bind(this))

      return framesPromises.then(function (frames) {
        return frames
      })
    }.bind(this))
  },

  buildOpbeatFrame: function buildOpbeatFrame (stack) {
    return new Promise(function (resolve, reject) {
      if (!stack.fileName && !stack.lineNumber) {
        // Probably an stack from IE, return empty frame as we can't use it.
        return resolve({})
      }

      var filePath = this.cleanFilePath(stack.fileName)
      var fileName = this.filePathToFileName(filePath)

      if(this.isFileInline(filePath)) {
        fileName = '(inline script)'
      }

      // Build Opbeat frame data
      var frame = {
        'filename': fileName,
        'lineno': stack.lineNumber,
        'colno': stack.columnNumber,
        'function': stack.functionName || '<anonymous>',
        'abs_path': stack.fileName,
        'in_app': this.isFileInApp(filePath)
      }

      // Detect Sourcemaps
      var sourceMapResolver = context.getFileSourceMapUrl(filePath)

      sourceMapResolver.then(function (sourceMapUrl) {
        frame.sourcemap_url = sourceMapUrl
        resolve(frame)
      })['catch'](function () {
        // // Resolve contexts if no source map
        var filePath = this.cleanFilePath(stack.fileName)
        var contextsResolver = context.getExceptionContexts(filePath, stack.lineNumber)

        contextsResolver.then(function (contexts) {
          frame.pre_context = contexts.preContext
          frame.context_line = contexts.contextLine
          frame.post_context = contexts.postContext

          resolve(frame)
        })['catch'](function () {
          resolve(frame)
        })
      }.bind(this))
    }.bind(this))
  },

  stackInfoToOpbeatException: function (stackInfo) {
    return new Promise(function (resolve, reject) {
      if (stackInfo.stack && stackInfo.stack.length) {
        var framesPromises = promiseMapSeries(stackInfo.stack, function (stack, index) {
          return this.buildOpbeatFrame(stack)
        }.bind(this))

        framesPromises.then(function (frames) {
          stackInfo.frames = frames
          stackInfo.stack = null
          resolve(stackInfo)
        })
      } else {
        resolve(stackInfo)
      }
    }.bind(this))
  },

  processOpbeatException: function (exception) {
    var type = exception.type
    var message = String(exception.message) || 'Script error'
    var filePath = this.cleanFilePath(exception.fileurl)
    var fileName = this.filePathToFileName(filePath)
    var frames = exception.frames || []
    var culprit

    if (frames && frames.length) {
      // Opbeat.com expects frames oldest to newest and JS sends them as newest to oldest
      frames.reverse()
    } else if (fileName) {
      frames.push({
        filename: fileName,
        lineno: exception.lineno
      })
    }

    var stacktrace = {
      frames: frames
    }

    // Set fileName from last frame, if filename is missing
    if (!fileName && frames.length) {
      var lastFrame = frames[frames.length - 1]
      if (lastFrame.filename) {
        fileName = lastFrame.filename
      } else {
        // If filename empty, assume inline script
        fileName = '(inline script)'
      }
    }

    if(this.isFileInline(filePath)) {
      culprit = '(inline script)'
    } else {
      culprit = fileName
    }

    var data = {
      message: type + ': ' + message,
      culprit: culprit,
      exception: {
        type: type,
        value: message
      },
      http: {
        url: window.location.href
      },
      stacktrace: stacktrace,
      user: config.get('context.user'),
      level: null,
      logger: null,
      machine: null
    }

    data.extra = this.getBrowserSpecificMetadata()

    if (config.get('context.extra')) {
      data.extra = utils.mergeObject(data.extra, config.get('context.extra'))
    }

    logger.log('opbeat.exceptions.processOpbeatException', data)
    transport.sendError(data)
  },

  cleanFilePath: function (filePath) {
    if (!filePath) {
      filePath = ''
    }

    if (filePath === '<anonymous>') {
      filePath = ''
    }

    return filePath
  },

  filePathToFileName: function (fileUrl) {
    var origin = window.location.origin || window.location.protocol + '//' + window.location.hostname + (window.location.port ? (':' + window.location.port) : '')

    if (fileUrl.indexOf(origin) > -1) {
      fileUrl = fileUrl.replace(origin + '/', '')
    }

    return fileUrl
  },

  isFileInline: function (fileUrl) {
    if(fileUrl) {
      return window.location.href.indexOf(fileUrl) === 0
    } else {
      return false
    }
  },

  isFileInApp: function (filename) {
    var pattern = config.get('libraryPathPattern')
    return !RegExp(pattern).test(filename)
  },

  getBrowserSpecificMetadata: function () {
    var viewportInfo = utils.getViewPortInfo()
    var extra = {
      'environment': {
        'utcOffset': new Date().getTimezoneOffset() / -60.0,
        'browserWidth': viewportInfo.width,
        'browserHeight': viewportInfo.height,
        'screenWidth': window.screen.width,
        'screenHeight': window.screen.height,
        'language': navigator.language,
        'userAgent': navigator.userAgent,
        'platform': navigator.platform
      },
      'page': {
        'referer': document.referrer,
        'host': document.domain,
        'location': window.location.href
      }
    }

    return extra
  }

}
<|endoftext|>


<commit_before>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Context Support
 *
 * Contexts, also known as frames, scopes or environments, are used to store
 * named resources. They provide an object-oriented replacement for global
 * variables. Contexts are immutable. New bindings are added by creating
 * "sub-contexts" with new bindings, from an existing parent context.
 * Sub-contexts inherit bindings from their parent.
 *
 * Contexts provide a form of inversion-of-control or dependendency-injection.
 * Normally, contexts are not explicitly used because FOAM's imports/exports
 * mechanism provides a high-level declarative method of dependency management
 * which hides their use.
 *
 * foam.__context__ references the root context, which is the ancestor of all other
 * contexts.
 */

(function() {
  var __context__ = {
    // Temporary: gets replaced in Window.js.
    assert: function(b) { console.assert.apply(console, arguments); return b; },

    /**
     * Lookup a class in the context.  Throws an exception if the value
     * couldn't be found, unless opt_suppress is true.
     *
     * @param id The id of the class to lookup.
     * @param opt_suppress Suppress throwing an error.
     **/
    lookup: function(id, opt_suppress) {
      var ret = typeof id === 'string' && this.__cache__[id];

      if ( ! opt_suppress ) {
        this.assert(
            ret,
            'Could not find any registered class for ' + id);
      }

      return foam.Function.isInstance(ret) ? ret() : ret;
    },

    /**
     * Register a class into the given context.  After registration
     * the class can be found again by calling foam.lookup('com.foo.SomeClass');
     *
     * @param cls    The class to register.
     * @param opt_id Optional id under which to register class.
     */
    register: function(cls, opt_id) {
      this.assert(
        typeof cls === 'object',
        'Cannot register non-objects into a context.');

      if ( opt_id ) {
        this.registerInCache_(cls, this.__cache__, opt_id);
      } else {
        this.assert(
            typeof cls.id === 'string',
            'Must have an .id property to be registered in a context.');

        this.registerInCache_(cls, this.__cache__, cls.id);

        if ( cls.package === 'foam.core' ) {
          this.registerInCache_(cls, this.__cache__, cls.name);
        }
      }
    },

    /**
     * Register a class factory into the given context.
     * When the class is first accessed the factory is used
     * to create the value which is used.
     */
    registerFactory: function(m, factory) {
      this.registerInCache_(factory, this.__cache__, m.id);

      if ( m.package === 'foam.core' ) {
        this.registerInCache_(factory, this.__cache__, m.name);
      }
    },

    /** Internal method to register a context binding in an internal cache */
    registerInCache_: function registerInCache_(cls, cache, name) {
      var old = cache[name];

      // Okay to replace a function with an actual class.
      // This happens after a lazy class is initialized.
      console.assert(
          ! old ||
              (foam.Function.isInstance(old) && ! foam.Function.isInstance(cls)),
          cls.id + ' is already registerd in this context.');

      cache[name] = cls;
    },

    /** Internal method to create a slot name for a specified key. */
    toSlotName_: foam.Function.memoize1(function toSlotName_(key) {
      return key + '$';
    }),

    /**
     * Creates a sub context of the context that this is called upon.
     * @param opt_args A map of bindings to set up in the sub context.
     *     Currently unused.
     */
    createSubContext: function createSubContext(opt_args, opt_name) {
      if ( ! opt_args ) return this;

      this.assert(
          opt_name === undefined || typeof opt_name === 'string',
          'opt_name must be left undefined or be a string.');

      var sub = Object.create(this);

      if ( opt_name ) {
        Object.defineProperty(sub, 'NAME', {
          value: opt_name,
          enumerable: false
        });
      }

      for ( var key in opt_args ) {
        if ( opt_args.hasOwnProperty(key) ) {
          var v = opt_args[key];

          if ( ! foam.core.Slot.isInstance(v) ) {
            Object.defineProperty(sub, this.toSlotName_(key), {
              value: foam.core.ConstantSlot.create({ value: v })
            });

            (function(v) {
              Object.defineProperty(sub, key, {
                value: v
              });
            })(v);
          } else {
            Object.defineProperty(sub, this.toSlotName_(key), {
              value: v
            });

            (function(v) {
              Object.defineProperty(sub, key, {
                get: function() { return v.get(); },
                enumerable: false
              });
            })(v);
          }
        }
      }

      Object.defineProperty(sub, '__cache__', {
        value: Object.create(this.__cache__),
        enumerable: false
      });

      foam.Object.freeze(sub);

      return sub;
    }
  };

  Object.defineProperty(__context__, '__cache__', {
    value: {},
    enumerable: false
  });

  // Create short-cuts for foam.__context__.[createSubContext, register, lookup]
  // in foam.
  foam.lookup = function(id, opt_suppress) {
    return foam.__context__.lookup(id, opt_suppress);
  };
  foam.register = function(cls) { foam.__context__.register(cls); };
  foam.createSubContext = function(opt_args, opt_name) {
    return foam.__context__.createSubContext(opt_args, opt_name);
  };

  foam.__context__ = __context__;
})();
<commit_msg>Fix registerInCache_ to allow a class to be overriden in a sub-context.<commit_after>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Context Support
 *
 * Contexts, also known as frames, scopes or environments, are used to store
 * named resources. They provide an object-oriented replacement for global
 * variables. Contexts are immutable. New bindings are added by creating
 * "sub-contexts" with new bindings, from an existing parent context.
 * Sub-contexts inherit bindings from their parent.
 *
 * Contexts provide a form of inversion-of-control or dependendency-injection.
 * Normally, contexts are not explicitly used because FOAM's imports/exports
 * mechanism provides a high-level declarative method of dependency management
 * which hides their use.
 *
 * foam.__context__ references the root context, which is the ancestor of all other
 * contexts.
 */

(function() {
  var __context__ = {
    // Temporary: gets replaced in Window.js.
    assert: function(b) { console.assert.apply(console, arguments); return b; },

    /**
     * Lookup a class in the context.  Throws an exception if the value
     * couldn't be found, unless opt_suppress is true.
     *
     * @param id The id of the class to lookup.
     * @param opt_suppress Suppress throwing an error.
     **/
    lookup: function(id, opt_suppress) {
      var ret = typeof id === 'string' && this.__cache__[id];

      if ( ! opt_suppress ) {
        this.assert(
            ret,
            'Could not find any registered class for ' + id);
      }

      return foam.Function.isInstance(ret) ? ret() : ret;
    },

    /**
     * Register a class into the given context.  After registration
     * the class can be found again by calling foam.lookup('com.foo.SomeClass');
     *
     * @param cls    The class to register.
     * @param opt_id Optional id under which to register class.
     */
    register: function(cls, opt_id) {
      this.assert(
        typeof cls === 'object',
        'Cannot register non-objects into a context.');

      if ( opt_id ) {
        this.registerInCache_(cls, this.__cache__, opt_id);
      } else {
        this.assert(
            typeof cls.id === 'string',
            'Must have an .id property to be registered in a context.');

        this.registerInCache_(cls, this.__cache__, cls.id);

        if ( cls.package === 'foam.core' ) {
          this.registerInCache_(cls, this.__cache__, cls.name);
        }
      }
    },

    /**
     * Register a class factory into the given context.
     * When the class is first accessed the factory is used
     * to create the value which is used.
     */
    registerFactory: function(m, factory) {
      this.registerInCache_(factory, this.__cache__, m.id);

      if ( m.package === 'foam.core' ) {
        this.registerInCache_(factory, this.__cache__, m.name);
      }
    },

    /** Internal method to register a context binding in an internal cache */
    registerInCache_: function registerInCache_(cls, cache, name) {
      var hasOld = Object.prototype.hasOwnProperty.call(cache, name);
      var old = cache[name];

      // Okay to replace a function with an actual class.
      // This happens after a lazy class is initialized.
      console.assert(
          ! hasOld ||
              (foam.Function.isInstance(old) && ! foam.Function.isInstance(cls)),
          cls.id + ' is already registered in this context.');

      cache[name] = cls;
    },

    /** Internal method to create a slot name for a specified key. */
    toSlotName_: foam.Function.memoize1(function toSlotName_(key) {
      return key + '$';
    }),

    /**
     * Creates a sub context of the context that this is called upon.
     * @param opt_args A map of bindings to set up in the sub context.
     *     Currently unused.
     */
    createSubContext: function createSubContext(opt_args, opt_name) {
      if ( ! opt_args ) return this;

      this.assert(
          opt_name === undefined || typeof opt_name === 'string',
          'opt_name must be left undefined or be a string.');

      var sub = Object.create(this);

      if ( opt_name ) {
        Object.defineProperty(sub, 'NAME', {
          value: opt_name,
          enumerable: false
        });
      }

      for ( var key in opt_args ) {
        if ( opt_args.hasOwnProperty(key) ) {
          var v = opt_args[key];

          if ( ! foam.core.Slot.isInstance(v) ) {
            Object.defineProperty(sub, this.toSlotName_(key), {
              value: foam.core.ConstantSlot.create({ value: v })
            });

            (function(v) {
              Object.defineProperty(sub, key, {
                value: v
              });
            })(v);
          } else {
            Object.defineProperty(sub, this.toSlotName_(key), {
              value: v
            });

            (function(v) {
              Object.defineProperty(sub, key, {
                get: function() { return v.get(); },
                enumerable: false
              });
            })(v);
          }
        }
      }

      Object.defineProperty(sub, '__cache__', {
        value: Object.create(this.__cache__),
        enumerable: false
      });

      foam.Object.freeze(sub);

      return sub;
    }
  };

  Object.defineProperty(__context__, '__cache__', {
    value: {},
    enumerable: false
  });

  // Create short-cuts for foam.__context__.[createSubContext, register, lookup]
  // in foam.
  foam.lookup = function(id, opt_suppress) {
    return foam.__context__.lookup(id, opt_suppress);
  };
  foam.register = function(cls) { foam.__context__.register(cls); };
  foam.createSubContext = function(opt_args, opt_name) {
    return foam.__context__.createSubContext(opt_args, opt_name);
  };

  foam.__context__ = __context__;
})();
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyDAO',
  extends: 'foam.dao.AbstractDAO',

  requires: [
    'foam.dao.ProxyListener'
  ],

  documentation: 'Proxy implementation for the DAO interface.',

  properties: [
    {
      class: 'Proxy',
      of: 'foam.dao.DAO',
      name: 'delegate',
      forwards: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'cmd_' ],
      topics: [ 'on' ], // TODO: Remove this when all users of it are updated.
      factory: function() { return foam.dao.NullDAO.create() },
      postSet: function(old, nu) {
        if ( old ) this.on.reset.pub();
      }
    },
    {
      name: 'of',
      factory: function() {
        return this.delegate.of;
      }
    }
  ],

  methods: [
    {
      name: 'getOf',
      javaReturns: 'foam.core.ClassInfo',
      javaCode: 'if ( of_ == null && getDelegate() != null ) return getDelegate().getOf(); return of_;'
    },

    {
      name: 'listen_',
      code: function listen_(x, sink, predicate) {
        var listener = this.ProxyListener.create({
          delegate: sink,
          args: [ predicate ]
        });

        listener.onDetach(listener.dao$.follow(this.delegate$));

        return listener;
      },
      javaCode: `
// TODO: Support changing of delegate
getDelegate().listen_(x, sink, predicate);
`
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyListener',

  implements: ['foam.dao.Sink'],

  properties: [
    'args',
    'delegate',
    {
      name: 'innerSub',
      postSet: function(_, s) {
        if (s) this.onDetach(s);
      }
    },
    {
      name: 'dao',
      postSet: function(old, nu) {
        this.innerSub && this.innerSub.detach();
        this.innerSub = nu && nu.listen.apply(nu, [this].concat(this.args));
        if ( old ) this.reset();
      }
    }
  ],

  methods: [
    function put(obj, s) {
      this.delegate.put(obj, this);
    },

    function remove(obj, s) {
      this.delegate.remove(obj, this);
    },

    function reset(s) {
      this.delegate.reset(this);
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ArraySink',
  extends: 'foam.dao.AbstractSink',

  constants: {
    // Dual to outputJSON method.
    //
    // TODO(markdittmer): Turn into static method: "parseJSON" once
    // https://github.com/foam-framework/foam2/issues/613 is fixed.
    PARSE_JSON: function(json, opt_cls, opt_ctx) {
      var cls = json.of || opt_cls;
      var array = json.array;
      if ( ! array ) return foam.dao.ArraySink.create({ of: cls }, opt_ctx);
      if ( foam.typeOf(cls) === foam.String )
        cls = ( opt_ctx || foam ).lookup(cls);

      return foam.dao.ArraySink.create({
        of: cls,
        array: foam.json.parse(array, cls, opt_ctx)
      }, opt_ctx);
    }
  },

  properties: [
    {
      class: 'List',
      name: 'array',
      adapt: function(old, nu) {
        if ( ! this.of ) return nu;
        var cls = this.of;
        for ( var i = 0; i < nu.length; i++ ) {
          if ( ! cls.isInstance(nu[i]) )
            nu[i] = cls.create(nu[i], this.__subContext__);
        }
        return nu;
      },
      factory: function() { return []; },
      javaFactory: `return new java.util.ArrayList();`
    },
    {
      class: 'Class',
      name: 'of',
      value: null
    },
    {
      name: 'a',
      transient: true,
      getter: function() {
        this.warn('Use of deprecated ArraySink.a');
        return this.array;
      }
    }
  ],

  methods: [
    {
      name: 'put',
      code: function put(o, sub) {
        var cls = this.of;
        if ( ! cls ) {
          this.array.push(o);
          return;
        }
        if ( cls.isInstance(o) )
          this.array.push(o);
        else
          this.array.push(cls.create(o, this.__subContext__));
      },
      javaCode: `getArray().add(obj);`
    },
    function outputJSON(outputter) {
      outputter.start('{');
      var outputClassName = outputter.outputClassNames;
      if ( outputClassName ) {
        outputter.nl().indent().out(
            outputter.maybeEscapeKey('class'), ':', outputter.postColonStr, '"',
            this.cls_.id, '"');
      }

      var array = this.array;
      var outputComma = outputClassName;
      if ( this.of ) {
        outputter.outputProperty(this, this.OF, outputComma);
        outputComma = true;
      }
      if ( array.length > 0 ) {
        if ( outputComma ) outputter.out(',');
        outputter.nl().indent().outputPropertyName(this.ARRAY).
            out(':', outputter.postColonStr).output(array, this.of);
      }
      outputter.nl().end('}');
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'PromisedDAO',
  extends: 'foam.dao.AbstractDAO',

  properties: [
    {
      class: 'Promised',
      of: 'foam.dao.DAO',
      methods: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'listen_', 'cmd_' ],
      name: 'promise'
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'LocalStorageDAO',
  extends: 'foam.dao.ArrayDAO',

  properties: [
    {
      name:  'name',
      label: 'Store Name',
      class:  'foam.core.String'
    }
  ],

  methods: [
    function init() {
      var objs = localStorage.getItem(this.name);
      if ( objs ) this.array = foam.json.parseString(objs, this);

      this.on.put.sub(this.updated);
      this.on.remove.sub(this.updated);

      // TODO: base on an indexed DAO
    }
  ],

  listeners: [
    {
      name: 'updated',
      isMerged: true,
      mergeDelay: 100,
      code: function() {
        localStorage.setItem(this.name, foam.json.stringify(this.array));
      }
    }
  ]
});

foam.LIB({
  name: 'foam.String',
  methods: [
    {
      name: 'daoize',
      code: foam.Function.memoize1(function(str) {
        // Turns SomeClassName into someClassNameDAO,
        // of package.ClassName into package.ClassNameDAO
        return str.substring(0, 1).toLowerCase() + str.substring(1) + 'DAO';
      })
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'InvalidArgumentException',
  extends: 'foam.dao.ExternalException',

  properties: [
    {
      class: 'String',
      name: 'message'
    }
  ]
});
<commit_msg>ProxyListener, just serialize the delegate, ProxyListener adds no value serialized.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyDAO',
  extends: 'foam.dao.AbstractDAO',

  requires: [
    'foam.dao.ProxyListener'
  ],

  documentation: 'Proxy implementation for the DAO interface.',

  properties: [
    {
      class: 'Proxy',
      of: 'foam.dao.DAO',
      name: 'delegate',
      forwards: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'cmd_' ],
      topics: [ 'on' ], // TODO: Remove this when all users of it are updated.
      factory: function() { return foam.dao.NullDAO.create() },
      postSet: function(old, nu) {
        if ( old ) this.on.reset.pub();
      }
    },
    {
      name: 'of',
      factory: function() {
        return this.delegate.of;
      }
    }
  ],

  methods: [
    {
      name: 'getOf',
      javaReturns: 'foam.core.ClassInfo',
      javaCode: 'if ( of_ == null && getDelegate() != null ) return getDelegate().getOf(); return of_;'
    },

    {
      name: 'listen_',
      code: function listen_(x, sink, predicate) {
        var listener = this.ProxyListener.create({
          delegate: sink,
          args: [ predicate ]
        });

        listener.onDetach(listener.dao$.follow(this.delegate$));

        return listener;
      },
      javaCode: `
// TODO: Support changing of delegate
getDelegate().listen_(x, sink, predicate);
`
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyListener',

  implements: ['foam.dao.Sink'],

  properties: [
    'args',
    'delegate',
    {
      name: 'innerSub',
      postSet: function(_, s) {
        if (s) this.onDetach(s);
      }
    },
    {
      name: 'dao',
      postSet: function(old, nu) {
        this.innerSub && this.innerSub.detach();
        this.innerSub = nu && nu.listen.apply(nu, [this].concat(this.args));
        if ( old ) this.reset();
      }
    }
  ],

  methods: [
    function outputJSON(outputter) {
      outputter.output(this.delegate);
    },

    function put(obj, s) {
      this.delegate.put(obj, this);
    },

    function remove(obj, s) {
      this.delegate.remove(obj, this);
    },

    function reset(s) {
      this.delegate.reset(this);
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ArraySink',
  extends: 'foam.dao.AbstractSink',

  constants: {
    // Dual to outputJSON method.
    //
    // TODO(markdittmer): Turn into static method: "parseJSON" once
    // https://github.com/foam-framework/foam2/issues/613 is fixed.
    PARSE_JSON: function(json, opt_cls, opt_ctx) {
      var cls = json.of || opt_cls;
      var array = json.array;
      if ( ! array ) return foam.dao.ArraySink.create({ of: cls }, opt_ctx);
      if ( foam.typeOf(cls) === foam.String )
        cls = ( opt_ctx || foam ).lookup(cls);

      return foam.dao.ArraySink.create({
        of: cls,
        array: foam.json.parse(array, cls, opt_ctx)
      }, opt_ctx);
    }
  },

  properties: [
    {
      class: 'List',
      name: 'array',
      adapt: function(old, nu) {
        if ( ! this.of ) return nu;
        var cls = this.of;
        for ( var i = 0; i < nu.length; i++ ) {
          if ( ! cls.isInstance(nu[i]) )
            nu[i] = cls.create(nu[i], this.__subContext__);
        }
        return nu;
      },
      factory: function() { return []; },
      javaFactory: `return new java.util.ArrayList();`
    },
    {
      class: 'Class',
      name: 'of',
      value: null
    },
    {
      name: 'a',
      transient: true,
      getter: function() {
        this.warn('Use of deprecated ArraySink.a');
        return this.array;
      }
    }
  ],

  methods: [
    {
      name: 'put',
      code: function put(o, sub) {
        var cls = this.of;
        if ( ! cls ) {
          this.array.push(o);
          return;
        }
        if ( cls.isInstance(o) )
          this.array.push(o);
        else
          this.array.push(cls.create(o, this.__subContext__));
      },
      javaCode: `getArray().add(obj);`
    },
    function outputJSON(outputter) {
      outputter.start('{');
      var outputClassName = outputter.outputClassNames;
      if ( outputClassName ) {
        outputter.nl().indent().out(
            outputter.maybeEscapeKey('class'), ':', outputter.postColonStr, '"',
            this.cls_.id, '"');
      }

      var array = this.array;
      var outputComma = outputClassName;
      if ( this.of ) {
        outputter.outputProperty(this, this.OF, outputComma);
        outputComma = true;
      }
      if ( array.length > 0 ) {
        if ( outputComma ) outputter.out(',');
        outputter.nl().indent().outputPropertyName(this.ARRAY).
            out(':', outputter.postColonStr).output(array, this.of);
      }
      outputter.nl().end('}');
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'PromisedDAO',
  extends: 'foam.dao.AbstractDAO',

  properties: [
    {
      class: 'Promised',
      of: 'foam.dao.DAO',
      methods: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'listen_', 'cmd_' ],
      name: 'promise'
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'LocalStorageDAO',
  extends: 'foam.dao.ArrayDAO',

  properties: [
    {
      name:  'name',
      label: 'Store Name',
      class:  'foam.core.String'
    }
  ],

  methods: [
    function init() {
      var objs = localStorage.getItem(this.name);
      if ( objs ) this.array = foam.json.parseString(objs, this);

      this.on.put.sub(this.updated);
      this.on.remove.sub(this.updated);

      // TODO: base on an indexed DAO
    }
  ],

  listeners: [
    {
      name: 'updated',
      isMerged: true,
      mergeDelay: 100,
      code: function() {
        localStorage.setItem(this.name, foam.json.stringify(this.array));
      }
    }
  ]
});

foam.LIB({
  name: 'foam.String',
  methods: [
    {
      name: 'daoize',
      code: foam.Function.memoize1(function(str) {
        // Turns SomeClassName into someClassNameDAO,
        // of package.ClassName into package.ClassNameDAO
        return str.substring(0, 1).toLowerCase() + str.substring(1) + 'DAO';
      })
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'InvalidArgumentException',
  extends: 'foam.dao.ExternalException',

  properties: [
    {
      class: 'String',
      name: 'message'
    }
  ]
});
<|endoftext|>


<commit_before>var fsm = angular.module('fsm', []);

fsm.directive('fsmStickyHeader', function(){
    return {
        restrict: 'EA',
        replace: false,
        scope: {
            scrollBody: '=',
            scrollStop: '=',
            scrollableContainer: '='
        },
        link: function(scope, element, attributes, control){
            var header = $(element, this);
            var content = $(scope.scrollBody);
            var scrollableContainer = $(scope.scrollableContainer) || $(window);
            var clonedHeader = null;

            function createClone(){
                /*
                 * switch place with cloned element, to keep binding intact
                 */
                clonedHeader = header;
                header = clonedHeader.clone();
                clonedHeader.after(header);
                clonedHeader.addClass('fsm-sticky-header');
                clonedHeader.css({
                    position: 'fixed',
                    'z-index': 10000,
                    visibility: 'hidden'
                });
                calculateSize();
            }

            function calculateSize() {
                clonedHeader.css({
                    top: scope.scrollStop,
                    width: header.outerWidth(),
                    left: header.offset().left
                });

                setColumnHeaderSizes();
            };

            function setColumnHeaderSizes() {
                if (clonedHeader.is('tr')) {
                    var clonedColumns = clonedHeader.find('th');
                    header.find('th').each(function (index, column) {
                        var clonedColumn = $(clonedColumns[index]);
                        clonedColumn.css( 'width', column.offsetWidth + 'px');
                    });
                }
            }

            function determineVisibility(){
                var scrollTop = scrollableContainer.scrollTop() + scope.scrollStop;
                var contentTop = content.position().top;
                var contentBottom = contentTop + content.outerHeight(false);

                if ( (scrollTop > contentTop) && (scrollTop < contentBottom) ) {
                    if (!clonedHeader){
                        createClone();
                        clonedHeader.css({ "visibility": "visible"});
                    }

                    if ( scrollTop < contentBottom && scrollTop > contentBottom - clonedHeader.outerHeight(false) ){
                        var top = contentBottom - scrollTop + scope.scrollStop - clonedHeader.outerHeight(false);
                        clonedHeader.css('top', top + 'px');
                    } else {
                        calculateSize();
                    }
                } else {
                    if (clonedHeader){
                        /*
                         * remove cloned element (switched places with original on creation)
                         */
                        header.remove();
                        header = clonedHeader;
                        clonedHeader = null;

                        header.removeClass('fsm-sticky-header');
                        header.css({
                            position: 'relative',
                            left: 0,
                            top: 0,
                            width: 'auto',
                            'z-index': 0,
                            visibility: 'visible'
                        });
                    }
                };
            };

            scrollableContainer.scroll(determineVisibility).trigger( "scroll" );
            scrollableContainer.resize(determineVisibility);
        }
    }
});

fsm.directive('fsmMenuButton', function(){
    return {
        restrict: 'EA',
        replace: false,
        scope: { },
        link: function(scope, element, attributes, control){
            var menuButton = $(element, this);

            menuButton.addClass('fsm-menu-button');
            menuButton.click( menuOnClick );

            function menuOnClick() {
                $('body').toggleClass('fsm-menu-toggle');
                setMenuSpin();
                setTimeout(setMenuSpin, 50);
            };

            function setMenuSpin(){
                menuButton.find('.fsm-menu-button-open').toggleClass('fsm-spin-forward');
                menuButton.find('.fsm-menu-button-closed').toggleClass('fsm-spin-backward');
            };
        }
    }
});

fsm.directive('fsmBigData', function ($filter) {

    return {
        restrict: 'AE',
        scope: {},
        replace: false,
        transclude: true,
        link: function (scope, element, attrs, controller, transclude) {
            var orderBy = $filter('orderBy');
            var currentPage = 0;
            var pagedDataName = attrs.fsmBigData.split(' of ')[0];
            var rightHandExpression = attrs.fsmBigData.split(' of ')[1];
            var pageSize = parseInt(rightHandExpression.split(' take ')[1]);
            var sourceData = rightHandExpression.split(' take ')[0];

            // Interesting things can be done here with the source object...
            // var displayGetter = $parse(sourceData);
            // var displaySetter = displayGetter.assign;
            // var results = orderBy(displayGetter(scope.$parent), sortColumns);
            // displaySetter(scope.$parent, results)

            var rawData = [];
            var sortedData = [];
            var pagedData = [];
            var page = $(window);
            var sortTypes = [ 'None', 'Ascending', 'Descending' ];
            var sortColumns = [];

            scope.sortTypes = sortTypes;

            transclude(scope, function (clone, transcludedScope) {
                element.append(clone);
                transcludedScope[pagedDataName] = pagedData;

                function nextPage() {
                    var dataSlice = sortedData.slice(pageSize * currentPage, (pageSize * (currentPage + 1)));
                    if (dataSlice.length > 0) {
                        pagedData.push.apply(pagedData, dataSlice);
                        currentPage++;
                    }
                }

                function addSortColumn(columnName, sortType) {

                    // If this column is currently in the sort stack, remove it.
                    for (var i = 0; i < sortColumns.length; i ++){
                        if (sortColumns[i].indexOf(columnName) > -1) {
                            sortColumns.splice(i, 1);
                        }
                    }

                    // Push this sort on the top of the stack (aka. array)
                    if (sortType > 0) {
                        var direction = '';
                        if (sortTypes[sortType] === 'Descending'){
                            direction = '-'
                        }
                        sortColumns.unshift(direction + columnName);
                    }

                    renderData();
                }

                function renderData() {
                    if (sortColumns.length){
                        sortedData = orderBy(rawData, sortColumns);
                    }
                    else {
                        sortedData = rawData;
                    }

                    pagedData.length = 0;
                    currentPage = 0;
                    nextPage();
                }

                function onPageScroll() {
                    var s = $(window).scrollTop(),
                    d = $(document).height(),
                    c = $(window).height();
                    scrollPercent = (s / (d-c));

                    if (scrollPercent > 0.98) {
                        // We use scope.apply here to tell angular about these changes because
                        // they happen outside of angularjs context... we're using jquery here
                        // to figure out when we need to load another page of data.
                        transcludedScope.$apply(nextPage);
                    }
                }

                page.scroll(onPageScroll).trigger( 'scroll' );

                scope.$parent.$watchCollection(sourceData, function (newData) {
                    if (newData){
                        rawData = newData;
                        renderData();
                    }
                });

                scope.addSortColumn = addSortColumn;
            });
        }
    }
});

fsm.directive('fsmSort', function () {
    var sortIconTemplate = '';

    return {
        restrict: 'AE',
        replace: false,
        scope: {},
        link: function (scope, element, attrs) {
            var columnHeader = element;
            var columnName = attrs.fsmSort;
            var sortIcon = angular.element(sortIconTemplate);
            columnHeader.append(' ')
            columnHeader.append(sortIcon);
            var currentSortType = 0;

            function swapIcons(){
                sortIcon.removeClass('fa-sort-desc fa-sort-asc fa-sort');

                var classToAdd = 'fa-sort';

                if (scope.$parent.sortTypes[currentSortType] === 'Ascending'){
                    classToAdd = 'fa-sort-asc';
                } else if(scope.$parent.sortTypes[currentSortType] === 'Descending') {
                    classToAdd = 'fa-sort-desc';
                }

                sortIcon.addClass(classToAdd);
            }

            columnHeader.css({ cursor: 'pointer' });

            columnHeader.bind('click', function() {
                // Find the kind of sort this should now be
                currentSortType ++;
                if (currentSortType == scope.$parent.sortTypes.length ){
                    currentSortType = 0;
                }

                scope.$apply( scope.$parent.addSortColumn(columnName, currentSortType) );

                swapIcons();
            });
        }
    }
});
<commit_msg>Fixed a small bug introduced my pull-request. It broke the default usage of just watching the scroll events on the browser window itself.<commit_after>var fsm = angular.module('fsm', []);

fsm.directive('fsmStickyHeader', function(){
    return {
        restrict: 'EA',
        replace: false,
        scope: {
            scrollBody: '=',
            scrollStop: '=',
            scrollableContainer: '='
        },
        link: function(scope, element, attributes, control){
            var header = $(element, this);
            var clonedHeader = null;
            var content = $(scope.scrollBody);
            var scrollableContainer = $(scope.scrollableContainer);

            if (scrollableContainer.length == 0){
                scrollableContainer = $(window);
            }

            function createClone(){
                /*
                 * switch place with cloned element, to keep binding intact
                 */
                clonedHeader = header;
                header = clonedHeader.clone();
                clonedHeader.after(header);
                clonedHeader.addClass('fsm-sticky-header');
                clonedHeader.css({
                    position: 'fixed',
                    'z-index': 10000,
                    visibility: 'hidden'
                });
                calculateSize();
            }

            function calculateSize() {
                clonedHeader.css({
                    top: scope.scrollStop,
                    width: header.outerWidth(),
                    left: header.offset().left
                });

                setColumnHeaderSizes();
            };

            function setColumnHeaderSizes() {
                if (clonedHeader.is('tr')) {
                    var clonedColumns = clonedHeader.find('th');
                    header.find('th').each(function (index, column) {
                        var clonedColumn = $(clonedColumns[index]);
                        clonedColumn.css( 'width', column.offsetWidth + 'px');
                    });
                }
            }

            function determineVisibility(){
                var scrollTop = scrollableContainer.scrollTop() + scope.scrollStop;
                var contentTop = content.position().top;
                var contentBottom = contentTop + content.outerHeight(false);

                if ( (scrollTop > contentTop) && (scrollTop < contentBottom) ) {
                    if (!clonedHeader){
                        createClone();
                        clonedHeader.css({ "visibility": "visible"});
                    }

                    if ( scrollTop < contentBottom && scrollTop > contentBottom - clonedHeader.outerHeight(false) ){
                        var top = contentBottom - scrollTop + scope.scrollStop - clonedHeader.outerHeight(false);
                        clonedHeader.css('top', top + 'px');
                    } else {
                        calculateSize();
                    }
                } else {
                    if (clonedHeader){
                        /*
                         * remove cloned element (switched places with original on creation)
                         */
                        header.remove();
                        header = clonedHeader;
                        clonedHeader = null;

                        header.removeClass('fsm-sticky-header');
                        header.css({
                            position: 'relative',
                            left: 0,
                            top: 0,
                            width: 'auto',
                            'z-index': 0,
                            visibility: 'visible'
                        });
                    }
                };
            };

            scrollableContainer.scroll(determineVisibility).trigger( "scroll" );
            scrollableContainer.resize(determineVisibility);
        }
    }
});

fsm.directive('fsmMenuButton', function(){
    return {
        restrict: 'EA',
        replace: false,
        scope: { },
        link: function(scope, element, attributes, control){
            var menuButton = $(element, this);

            menuButton.addClass('fsm-menu-button');
            menuButton.click( menuOnClick );

            function menuOnClick() {
                $('body').toggleClass('fsm-menu-toggle');
                setMenuSpin();
                setTimeout(setMenuSpin, 50);
            };

            function setMenuSpin(){
                menuButton.find('.fsm-menu-button-open').toggleClass('fsm-spin-forward');
                menuButton.find('.fsm-menu-button-closed').toggleClass('fsm-spin-backward');
            };
        }
    }
});

fsm.directive('fsmBigData', function ($filter) {

    return {
        restrict: 'AE',
        scope: {},
        replace: false,
        transclude: true,
        link: function (scope, element, attrs, controller, transclude) {
            var orderBy = $filter('orderBy');
            var currentPage = 0;
            var pagedDataName = attrs.fsmBigData.split(' of ')[0];
            var rightHandExpression = attrs.fsmBigData.split(' of ')[1];
            var pageSize = parseInt(rightHandExpression.split(' take ')[1]);
            var sourceData = rightHandExpression.split(' take ')[0];

            // Interesting things can be done here with the source object...
            // var displayGetter = $parse(sourceData);
            // var displaySetter = displayGetter.assign;
            // var results = orderBy(displayGetter(scope.$parent), sortColumns);
            // displaySetter(scope.$parent, results)

            var rawData = [];
            var sortedData = [];
            var pagedData = [];
            var page = $(window);
            var sortTypes = [ 'None', 'Ascending', 'Descending' ];
            var sortColumns = [];

            scope.sortTypes = sortTypes;

            transclude(scope, function (clone, transcludedScope) {
                element.append(clone);
                transcludedScope[pagedDataName] = pagedData;

                function nextPage() {
                    var dataSlice = sortedData.slice(pageSize * currentPage, (pageSize * (currentPage + 1)));
                    if (dataSlice.length > 0) {
                        pagedData.push.apply(pagedData, dataSlice);
                        currentPage++;
                    }
                }

                function addSortColumn(columnName, sortType) {

                    // If this column is currently in the sort stack, remove it.
                    for (var i = 0; i < sortColumns.length; i ++){
                        if (sortColumns[i].indexOf(columnName) > -1) {
                            sortColumns.splice(i, 1);
                        }
                    }

                    // Push this sort on the top of the stack (aka. array)
                    if (sortType > 0) {
                        var direction = '';
                        if (sortTypes[sortType] === 'Descending'){
                            direction = '-'
                        }
                        sortColumns.unshift(direction + columnName);
                    }

                    renderData();
                }

                function renderData() {
                    if (sortColumns.length){
                        sortedData = orderBy(rawData, sortColumns);
                    }
                    else {
                        sortedData = rawData;
                    }

                    pagedData.length = 0;
                    currentPage = 0;
                    nextPage();
                }

                function onPageScroll() {
                    var s = $(window).scrollTop(),
                    d = $(document).height(),
                    c = $(window).height();
                    scrollPercent = (s / (d-c));

                    if (scrollPercent > 0.98) {
                        // We use scope.apply here to tell angular about these changes because
                        // they happen outside of angularjs context... we're using jquery here
                        // to figure out when we need to load another page of data.
                        transcludedScope.$apply(nextPage);
                    }
                }

                page.scroll(onPageScroll).trigger( 'scroll' );

                scope.$parent.$watchCollection(sourceData, function (newData) {
                    if (newData){
                        rawData = newData;
                        renderData();
                    }
                });

                scope.addSortColumn = addSortColumn;
            });
        }
    }
});

fsm.directive('fsmSort', function () {
    var sortIconTemplate = '';

    return {
        restrict: 'AE',
        replace: false,
        scope: {},
        link: function (scope, element, attrs) {
            var columnHeader = element;
            var columnName = attrs.fsmSort;
            var sortIcon = angular.element(sortIconTemplate);
            columnHeader.append(' ')
            columnHeader.append(sortIcon);
            var currentSortType = 0;

            function swapIcons(){
                sortIcon.removeClass('fa-sort-desc fa-sort-asc fa-sort');

                var classToAdd = 'fa-sort';

                if (scope.$parent.sortTypes[currentSortType] === 'Ascending'){
                    classToAdd = 'fa-sort-asc';
                } else if(scope.$parent.sortTypes[currentSortType] === 'Descending') {
                    classToAdd = 'fa-sort-desc';
                }

                sortIcon.addClass(classToAdd);
            }

            columnHeader.css({ cursor: 'pointer' });

            columnHeader.bind('click', function() {
                // Find the kind of sort this should now be
                currentSortType ++;
                if (currentSortType == scope.$parent.sortTypes.length ){
                    currentSortType = 0;
                }

                scope.$apply( scope.$parent.addSortColumn(columnName, currentSortType) );

                swapIcons();
            });
        }
    }
});
<|endoftext|>


<commit_before>/**
 *  FullScreen widget:
 *
 *  var widget = new cdb.ui.common.FullScreen({
 *    doc: ".container", // optional; if not specified, we do the fullscreen of the whole window
 *    template: this.getTemplate("table/views/fullscreen")
 *  });
 *
 */

cdb.ui.common.FullScreen = cdb.core.View.extend({

  tagName: 'div',
  className: 'cartodb-fullscreen',

  events: {

    "click a": "_toggleFullScreen"

  },

  initialize: function() {

    _.bindAll(this, 'render');
    _.defaults(this.options, this.default_options);

    //this.model = new cdb.core.Model({
      //allowWheelOnFullscreen: false
    //});

    this._addWheelEvent();

  },

  _addWheelEvent: function() {

      var self    = this;
      var mapView = this.options.mapView;

      $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() {

        if ( !document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
          if (self.model.get("allowWheelOnFullscreen")) {
            mapView.options.map.set("scrollwheel", false);
          }
        }

        mapView.invalidateSize();

      });

  },

  _toggleFullScreen: function(ev) {

    ev.stopPropagation();
    ev.preventDefault();

    var doc   = window.document;
    var docEl = doc.documentElement;

    if (this.options.doc) { // we use a custom element
      docEl = $(this.options.doc)[0];
    }

    var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
    var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;

    var mapView = this.options.mapView;

    if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {

      requestFullScreen.call(docEl);

      if (mapView) {

        if (this.model.get("allowWheelOnFullscreen")) {
          mapView.options.map.set("scrollwheel", true);
        }

      }

    } else {

      cancelFullScreen.call(doc);

    }
  },

  render: function() {

    var $el = this.$el;

    var options = _.extend(this.options);

    $el.html(this.options.template(options));

    return this;
  }

});
<commit_msg>fixes #235-fullscreen<commit_after>/**
 *  FullScreen widget:
 *
 *  var widget = new cdb.ui.common.FullScreen({
 *    doc: ".container", // optional; if not specified, we do the fullscreen of the whole window
 *    template: this.getTemplate("table/views/fullscreen")
 *  });
 *
 */

cdb.ui.common.FullScreen = cdb.core.View.extend({

  tagName: 'div',
  className: 'cartodb-fullscreen',

  events: {

    "click a": "_toggleFullScreen"

  },

  initialize: function() {

    _.bindAll(this, 'render');
    _.defaults(this.options, this.default_options);

    //this.model = new cdb.core.Model({
      //allowWheelOnFullscreen: false
    //});

    this._addWheelEvent();

  },

  _addWheelEvent: function() {

      var self    = this;
      var mapView = this.options.mapView;

      $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() {

        if ( !document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
          if (self.model.get("allowWheelOnFullscreen")) {
            mapView.options.map.set("scrollwheel", false);
          }
        }

        mapView.invalidateSize();

      });

  },

  _toggleFullScreen: function(ev) {

    ev.stopPropagation();
    ev.preventDefault();

    var doc   = window.document;
    var docEl = doc.documentElement;

    if (this.options.doc) { // we use a custom element
      docEl = $(this.options.doc)[0];
    }

    var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
    var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;

    var mapView = this.options.mapView;

    if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {

      if (docEl.webkitRequestFullScreen) {
        requestFullScreen.call(docEl, Element.ALLOW_KEYBOARD_INPUT);
      } else {
        requestFullScreen.call(docEl);
      }

      if (mapView) {

        if (this.model.get("allowWheelOnFullscreen")) {
          mapView.options.map.set("scrollwheel", true);
        }

      }

    } else {

      cancelFullScreen.call(doc);

    }
  },

  render: function() {

    var $el = this.$el;

    var options = _.extend(this.options);

    $el.html(this.options.template(options));

    return this;
  }

});
<|endoftext|>


<commit_before>const Util = require('../../util');
const FIELD_ORIGIN = '_origin';

function isChange(preShapes, shapes) {
  if (!preShapes) {
    return true;
  }

  if (preShapes.length !== shapes.length) {
    return true;
  }

  let rst = false;
  Util.each(shapes, (shape, index) => {
    if (!Util.isEqual(shape, preShapes[index])) {
      rst = true;
      return false;
    }
  });
  return rst;
}

const ActiveMixin = {
  _onPlotmove(ev) {
    const self = this;
    const view = self.get('view');
    const canvas = view.get('canvas');
    const type = Util.lowerFirst(self.get('type'));
    const point = {
      x: ev.x,
      y: ev.y
    };
    let shapes;
    if (Util.inArray([ 'line', 'path' ], type) || !self.isShareTooltip()) {
      shapes = self.getSingleShapeByPoint(point);
    } else if (type !== 'area') {
      shapes = self.getGroupShapesByPoint(point);
    }

    if (shapes) {
      self.setShapesActived(shapes);
    } else {
      if (self.get('activeShapes')) {
        self.clearActivedShapes();
        canvas.draw();
      }
    }
  },
  _bindActiveAction() {
    const self = this;
    const view = self.get('view');
    view.on('plotmove', Util.wrapBehavior(self, '_onPlotmove'));
  },
  _offActiveAction() {
    const self = this;
    const view = self.get('view');
    view.off('plotmove', Util.getWrapBehavior(self, '_onPlotmove'));
  },
  _setActiveShape(shape) {
    const self = this;
    const shapeData = shape.get('origin');
    let shapeName = shapeData.shape || self.getDefaultValue('shape');
    if (Util.isArray(shapeName)) {
      shapeName = shapeName[0];
    }
    const shapeFactory = self.get('shapeFactory');
    const shapeCfg = Util.mix({}, shape.__attrs, {
      origin: shapeData
    });
    const activeCfg = shapeFactory.getActiveCfg(shapeName, shapeCfg);
    Util.mix(shape.__attrs, activeCfg);
    shape.setZIndex(1); // 提前
  },
  setShapesActived(shapes) {
    const self = this;
    const type = self.get('type');
    if (!Util.isArray(shapes)) {
      shapes = [ shapes ];
    }

    const preShapes = self.get('preShapes'); // 获取上次被激活的 shapes
    if (!isChange(preShapes, shapes)) {
      return;
    }
    if (preShapes) {
      self.clearActivedShapes(); // 先清除激活元素
    }
    const view = self.get('view');
    const canvas = view.get('canvas');
    const shapeContainer = self.get('shapeContainer');
    Util.each(shapes, shape => {
      if (!shape.get('_originAttrs')) {
        shape.set('_originAttrs', Util.cloneDeep(shape.__attrs)); // 缓存原来的属性，由于 __attrs.matrix 是数组，所以此处需要深度复制
      }
      if (shape.get('visible') && !shape.get('selected')) {
        self._setActiveShape(shape);
      }
    });
    // 抛出事件
    view.emit(type + ':active', {
      geom: self,
      shapes
    });
    self.set('activeShapes', shapes);
    self.set('preShapes', shapes);
    shapeContainer.sort();
    canvas.draw();
  },
  clearActivedShapes() {
    const self = this;
    const shapeContainer = self.get('shapeContainer');
    if (shapeContainer && !shapeContainer.get('destroyed')) {
      const activeShapes = self.get('activeShapes');
      Util.each(activeShapes, activeShape => {
        if (!activeShape.get('selected')) {
          const originAttrs = activeShape.get('_originAttrs');
          activeShape.__attrs = Util.cloneDeep(originAttrs);
          activeShape.setZIndex(0);
          activeShape.set('_originAttrs', null);
        }
      });
      const preHighlightShapes = self.get('preHighlightShapes');
      if (preHighlightShapes) {
        const shapes = self.getShapes();
        Util.each(shapes, shape => {
          if (!shape.get('selected')) {
            const originAttrs = shape.get('_originAttrs');
            shape.__attrs = Util.cloneDeep(originAttrs);
            shape.setZIndex(0);
            shape.set('_originAttrs', null);
          }
        });
      }
      // 恢复原来排序
      const children = shapeContainer.get('children');
      children.sort((obj1, obj2) => {
        return obj1._INDEX - obj2._INDEX;
      });

      self.set('activeShapes', null);
      self.set('preShapes', null);
      self.set('preHighlightShapes', null);
    }
  },
  getGroupShapesByPoint(point) {
    const self = this;
    const shapeContainer = self.get('shapeContainer');
    const activeShapes = [];
    if (shapeContainer) {
      const xField = self.getXScale().field;
      const shapes = self.getShapes();
      const originObj = self._getOriginByPoint(point);
      Util.each(shapes, shape => {
        const origin = shape.get('origin');
        if (shape.get('visible') && origin) { // 有可能不是图形，而是label文本，所以判断一下
          const shapeXValue = origin[FIELD_ORIGIN][xField];
          if (shapeXValue === originObj[xField]) {
            activeShapes.push(shape);
          }
        }
      });
    }
    return activeShapes;
  },
  getSingleShapeByPoint(point) {
    const self = this;
    const shapeContainer = self.get('shapeContainer');
    const canvas = shapeContainer.get('canvas');
    const pixelRatio = canvas.get('pixelRatio');
    let result;
    if (shapeContainer) {
      result = shapeContainer.getShape(point.x * pixelRatio, point.y * pixelRatio);
    }

    if (result && result.get('origin')) {
      return result;
    }

  },
  highlightShapes(highlightShapes, highlightCfg) {
    const self = this;
    if (!Util.isArray(highlightShapes)) {
      highlightShapes = [ highlightShapes ];
    }

    const preHighlightShapes = self.get('preHighlightShapes'); // 获取上次被激活的 shapes
    if (!isChange(preHighlightShapes, highlightShapes)) {
      return;
    }

    if (preHighlightShapes) {
      self.clearActivedShapes();
    }

    const shapes = self.getShapes();

    Util.each(shapes, shape => {
      if (!shape.get('_originAttrs')) {
        shape.set('_originAttrs', Util.cloneDeep(shape.__attrs)); // 缓存原来的属性
      }
      if (Util.indexOf(highlightShapes, shape) !== -1) {
        shape.__attrs = Util.mix({}, shape.get('_originAttrs'), highlightCfg);
        shape.setZIndex(1); // 提前
      } else {
        Util.mix(shape.__attrs, {
          fill: '#fff',
          fillOpacity: 0.3,
          strokeOpacity: 0.3,
          stroke: '#fff'
        });
        shape.setZIndex(0);
      }
    });
    self.set('preHighlightShapes', highlightShapes);
    self.set('activeShapes', highlightShapes);
  }
};

module.exports = ActiveMixin;
<commit_msg>修复 highlight 接口问题。<commit_after>const Util = require('../../util');
const FIELD_ORIGIN = '_origin';

function isChange(preShapes, shapes) {
  if (!preShapes) {
    return true;
  }

  if (preShapes.length !== shapes.length) {
    return true;
  }

  let rst = false;
  Util.each(shapes, (shape, index) => {
    if (!Util.isEqual(shape, preShapes[index])) {
      rst = true;
      return false;
    }
  });
  return rst;
}

const ActiveMixin = {
  _onPlotmove(ev) {
    const self = this;
    const view = self.get('view');
    const canvas = view.get('canvas');
    const type = Util.lowerFirst(self.get('type'));
    const point = {
      x: ev.x,
      y: ev.y
    };
    let shapes;
    if (Util.inArray([ 'line', 'path' ], type) || !self.isShareTooltip()) {
      shapes = self.getSingleShapeByPoint(point);
    } else if (type !== 'area') {
      shapes = self.getGroupShapesByPoint(point);
    }

    if (shapes) {
      self.setShapesActived(shapes);
    } else {
      if (self.get('activeShapes')) {
        self.clearActivedShapes();
        canvas.draw();
      }
    }
  },
  _bindActiveAction() {
    const self = this;
    const view = self.get('view');
    view.on('plotmove', Util.wrapBehavior(self, '_onPlotmove'));
  },
  _offActiveAction() {
    const self = this;
    const view = self.get('view');
    view.off('plotmove', Util.getWrapBehavior(self, '_onPlotmove'));
  },
  _setActiveShape(shape) {
    const self = this;
    const shapeData = shape.get('origin');
    let shapeName = shapeData.shape || self.getDefaultValue('shape');
    if (Util.isArray(shapeName)) {
      shapeName = shapeName[0];
    }
    const shapeFactory = self.get('shapeFactory');
    const shapeCfg = Util.mix({}, shape.__attrs, {
      origin: shapeData
    });
    const activeCfg = shapeFactory.getActiveCfg(shapeName, shapeCfg);
    Util.mix(shape.__attrs, activeCfg);
    shape.setZIndex(1); // 提前
  },
  setShapesActived(shapes) {
    const self = this;
    const type = self.get('type');
    if (!Util.isArray(shapes)) {
      shapes = [ shapes ];
    }

    const preShapes = self.get('preShapes'); // 获取上次被激活的 shapes
    if (!isChange(preShapes, shapes)) {
      return;
    }
    if (preShapes) {
      self.clearActivedShapes(); // 先清除激活元素
    }
    const view = self.get('view');
    const canvas = view.get('canvas');
    const shapeContainer = self.get('shapeContainer');
    Util.each(shapes, shape => {
      if (!shape.get('_originAttrs')) {
        shape.set('_originAttrs', Util.cloneDeep(shape.__attrs)); // 缓存原来的属性，由于 __attrs.matrix 是数组，所以此处需要深度复制
      }
      if (shape.get('visible') && !shape.get('selected')) {
        self._setActiveShape(shape);
      }
    });
    // 抛出事件
    view.emit(type + ':active', {
      geom: self,
      shapes
    });
    self.set('activeShapes', shapes);
    self.set('preShapes', shapes);
    shapeContainer.sort();
    canvas.draw();
  },
  clearActivedShapes() {
    const self = this;
    const shapeContainer = self.get('shapeContainer');
    if (shapeContainer && !shapeContainer.get('destroyed')) {
      const activeShapes = self.get('activeShapes');
      Util.each(activeShapes, activeShape => {
        if (!activeShape.get('selected')) {
          const originAttrs = activeShape.get('_originAttrs');
          activeShape.__attrs = Util.cloneDeep(originAttrs);
          activeShape.setZIndex(0);
          activeShape.set('_originAttrs', null);
        }
      });
      const preHighlightShapes = self.get('preHighlightShapes');
      if (preHighlightShapes) {
        const shapes = shapeContainer.get('children');
        Util.each(shapes, shape => {
          if (!shape.get('selected')) {
            const originAttrs = shape.get('_originAttrs');
            if (originAttrs) {
              shape.__attrs = Util.cloneDeep(originAttrs);
              shape.setZIndex(0);
              shape.set('_originAttrs', null);
            }
          }
        });
      }
      // 恢复原来排序
      const children = shapeContainer.get('children');
      children.sort((obj1, obj2) => {
        return obj1._INDEX - obj2._INDEX;
      });

      self.set('activeShapes', null);
      self.set('preShapes', null);
      self.set('preHighlightShapes', null);
    }
  },
  getGroupShapesByPoint(point) {
    const self = this;
    const shapeContainer = self.get('shapeContainer');
    const activeShapes = [];
    if (shapeContainer) {
      const xField = self.getXScale().field;
      const shapes = self.getShapes();
      const originObj = self._getOriginByPoint(point);
      Util.each(shapes, shape => {
        const origin = shape.get('origin');
        if (shape.get('visible') && origin) { // 有可能不是图形，而是label文本，所以判断一下
          const shapeXValue = origin[FIELD_ORIGIN][xField];
          if (shapeXValue === originObj[xField]) {
            activeShapes.push(shape);
          }
        }
      });
    }
    return activeShapes;
  },
  getSingleShapeByPoint(point) {
    const self = this;
    const shapeContainer = self.get('shapeContainer');
    const canvas = shapeContainer.get('canvas');
    const pixelRatio = canvas.get('pixelRatio');
    let result;
    if (shapeContainer) {
      result = shapeContainer.getShape(point.x * pixelRatio, point.y * pixelRatio);
    }

    if (result && result.get('origin')) {
      return result;
    }

  },
  highlightShapes(highlightShapes, highlightCfg) {
    const self = this;
    if (!Util.isArray(highlightShapes)) {
      highlightShapes = [ highlightShapes ];
    }

    const preHighlightShapes = self.get('preHighlightShapes'); // 获取上次被激活的 shapes
    if (!isChange(preHighlightShapes, highlightShapes)) {
      return;
    }

    if (preHighlightShapes) {
      self.clearActivedShapes();
    }

    const shapes = self.getShapes();

    Util.each(shapes, shape => {
      if (!shape.get('_originAttrs')) {
        shape.set('_originAttrs', Util.cloneDeep(shape.__attrs)); // 缓存原来的属性
      }
      if (Util.indexOf(highlightShapes, shape) !== -1) {
        shape.__attrs = Util.mix({}, shape.get('_originAttrs'), highlightCfg);
        shape.setZIndex(1); // 提前
      } else {
        Util.mix(shape.__attrs, {
          fill: '#fff',
          fillOpacity: 0.3,
          strokeOpacity: 0.3,
          stroke: '#fff'
        });
        shape.setZIndex(0);
      }
    });
    self.set('preHighlightShapes', highlightShapes);
    self.set('activeShapes', highlightShapes);
  }
};

module.exports = ActiveMixin;
<|endoftext|>


<commit_before>  //////////////////////////////////////////////////////////////////////////////
/**
 * @module geo.gl
 */
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of polygonFeature
 *
 * @class
 * @returns {ggl.polygonFeature}
 */
//////////////////////////////////////////////////////////////////////////////
ggl.polygonFeature = function (arg) {
  'use strict';
  if (!(this instanceof ggl.polygonFeature)) {
    return new ggl.polygonFeature(arg);
  }
  arg = arg || {};
  geo.polygonFeature.call(this, arg);

  ////////////////////////////////////////////////////////////////////////////
  /**
   * @private
   */
  ////////////////////////////////////////////////////////////////////////////
  var m_this = this,
      m_actor = null,
      s_init = this._init,
      s_update = this._update;

  function createVertexShader() {
      var vertexShaderSource = [
        'attribute vec3 pos;',
        'attribute vec3 fillColor;',
        'attribute float fillOpacity;',
        'uniform mat4 modelViewMatrix;',
        'uniform mat4 projectionMatrix;',
        'uniform float pixelWidth;',
        'varying vec3 fillColorVar;',
        'varying float fillOpacityVar;',

        'void main(void)',
        '{',
        '  vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);',
        '  if (clipPos.w != 0.0) {',
        '    clipPos = clipPos/clipPos.w;',
        '  }',
        '  fillColorVar = fillColor;',
        '  fillOpacityVar = fillOpacity;',
        '  gl_Position = clipPos;',
        '}'
      ].join('\n'),
      shader = new vgl.shader(gl.VERTEX_SHADER);
      shader.setShaderSource(vertexShaderSource);
      return shader;
    }

  function createFragmentShader() {
    var fragmentShaderSource = [
      '#ifdef GL_ES',
      '  precision highp float;',
      '#endif',
      'varying vec3 fillColorVar;',
      'varying float fillOpacityVar;',
      'void main () {',
      '  gl_FragColor = vec4 (fillColorVar, fillOpacityVar);',
      '}'
    ].join('\n'),
    shader = new vgl.shader(gl.FRAGMENT_SHADER);
    shader.setShaderSource(fragmentShaderSource);
    return shader;
  }

  function createGLPolygons() {
    var i, numPolygons = m_this.data().length, p = null,
        numPts = null, start = null, itemIndex = 0,
        polygonItemCoordIndex = 0, position = [],
        polygonItemCoords = null, fillColor = [],
        fillOpacity = [], posFunc, fillColorFunc,
        polygonItem, fillOpacityFunc, buffers = vgl.DataBuffers(1024),
        sourcePositions = vgl.sourceDataP3fv(),
        sourceFillColor =
          vgl.sourceDataAnyfv(3, vgl.vertexAttributeKeysIndexed.Two),
        sourceFillOpacity =
          vgl.sourceDataAnyfv(1, vgl.vertexAttributeKeysIndexed.Three),
        trianglePrimitive = vgl.triangles(),
        mat = vgl.material(), blend = vgl.blend(),
        prog = vgl.shaderProgram(), vertexShader = createVertexShader(),
        fragmentShader = createFragmentShader(),
        posAttr = vgl.vertexAttribute('pos'),
        fillColorAttr = vgl.vertexAttribute('fillColor'),
        fillOpacityAttr = vgl.vertexAttribute('fillOpacity'),
        modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
        projectionUniform = new vgl.projectionUniform('projectionMatrix'),
        geom = vgl.geometryData(),
        mapper = vgl.mapper();

    posFunc = m_this.position();
    fillColorFunc = m_this.style.get('fillColor');
    fillOpacityFunc = m_this.style.get('fillOpacity');

    m_this.data().forEach(function (item) {
      var polygon = m_this.polygon()(item, itemIndex);
      polygonItem = polygon.outer;
      polygonItemCoordIndex = 0;

      var extRing = [], extIndex = 0, extLength = polygonItem.length - 1;
      extRing[0] = [];
      polygonItem.forEach(function (extRingCoords) {
        if (extIndex !== extLength) {
         //extRing = extRing.concat(extRingCoords);
         extRing[0].push({x: extRingCoords[0], y: extRingCoords[1]});
        }
        ++extIndex;
      });
      console.log("extRing ", extRing);
      //console.log("result", PolyK.Triangulate(extRing));
      var myTriangulator = new PNLTRI.Triangulator();
      var triangList = myTriangulator.triangulate_polygon( extRing );
      var newTriangList = [];
      //var result = PolyK.Triangulate(extRing)
      console.log(triangList);

      triangList.forEach(function (newIndices) {
        newTriangList = newTriangList.concat(newIndices);
      });

      newTriangList.forEach(function (polygonIndex) {
        polygonItemCoordIndex = polygonIndex;
        polygonItemCoords = polygonItem[polygonItemCoordIndex];

        console.log("polygonItemData ", polygonItemCoords);
        console.log("polygonItemData ", polygonItemCoordIndex);

        var p = posFunc(item, itemIndex,
                        polygonItemCoords, polygonItemCoordIndex);
          if (p instanceof geo.latlng) {
            position.push([p.x(), p.y(), p.z()]);
          } else {
            position.push([p.x, p.y, p.z || 0.0]);
          }
          var sc = fillColorFunc(item, itemIndex,
            polygonItemCoords, polygonItemCoordIndex);
          fillColor.push([sc.r, sc.g, sc.b]);
          fillOpacity.push(fillOpacityFunc(item,
            itemIndex, polygonItemCoords, polygonItemCoordIndex));
          polygonItemCoordIndex += 1;
        });
      itemIndex += 1;
    });

    position = geo.transform.transformCoordinates(
                 m_this.gcs(), m_this.layer().map().gcs(),
                 position, 3);

    buffers.create('pos', 3);
    buffers.create('indices', 1);
    buffers.create('fillColor', 3);
    buffers.create('fillOpacity', 1);

    numPts = position.length;

    // TODO: Right now this is ugly but we will fix it.
    prog.addVertexAttribute(posAttr,
      vgl.vertexAttributeKeys.Position);
    prog.addVertexAttribute(fillColorAttr,
      vgl.vertexAttributeKeysIndexed.Two);
    prog.addVertexAttribute(fillOpacityAttr,
      vgl.vertexAttributeKeysIndexed.Three);

    prog.addUniform(modelViewUniform);
    prog.addUniform(projectionUniform);

    prog.addShader(fragmentShader);
    prog.addShader(vertexShader);

    mat.addAttribute(prog);
    mat.addAttribute(blend);

    m_actor = vgl.actor();
    m_actor.setMaterial(mat);

    start = buffers.alloc(numPts);
    var currentIndex = start;

    console.log("numPts ", numPts);
    for (i = 0; i < numPts; i += 1) {
      buffers.write('pos', position[i], start + i, 1);
      buffers.write('indices', [i], start + i, 1);
      buffers.write('fillColor', fillColor[i], start + i, 1);
      buffers.write('fillOpacity', [fillOpacity[i]], start + i, 1);
    }

    console.log(buffers.get('fillColor'));
    sourcePositions.pushBack(buffers.get('pos'));
    geom.addSource(sourcePositions);

    sourceFillColor.pushBack(buffers.get('fillColor'));
    geom.addSource(sourceFillColor);

    sourceFillOpacity.pushBack(buffers.get('fillOpacity'));
    geom.addSource(sourceFillOpacity);

    console.log(buffers.get('indices'));
    trianglePrimitive.setIndices(buffers.get('indices'));
    geom.addPrimitive(trianglePrimitive);

    mapper.setGeometryData(geom);
    m_actor.setMapper(mapper);
  }

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Initialize
   */
  ////////////////////////////////////////////////////////////////////////////
  this._init = function (arg) {
    s_init.call(m_this, arg);
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Build
   *
   * @override
   */
  ////////////////////////////////////////////////////////////////////////////
  this._build = function () {
    if (m_actor) {
      m_this.renderer().contextRenderer().removeActor(m_actor);
    }

    createGLPolygons();

    m_this.renderer().contextRenderer().addActor(m_actor);
    m_this.buildTime().modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Update
   *
   * @override
   */
  ////////////////////////////////////////////////////////////////////////////
  this._update = function () {
    s_update.call(m_this);

    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
        m_this.updateTime().getMTime() <= m_this.getMTime()) {
      m_this._build();
    }

    m_actor.setVisible(m_this.visible());
    m_actor.material().setBinNumber(m_this.bin());
    m_this.updateTime().modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Destroy
   */
  ////////////////////////////////////////////////////////////////////////////
  this._exit = function () {
    m_this.renderer().contextRenderer().removeActor(m_actor);
  };

  this._init(arg);
  return this;
};

inherit(ggl.polygonFeature, geo.polygonFeature);

// Now register it
geo.registerFeature('vgl', 'polygon', ggl.polygonFeature);
<commit_msg>Implementing holes now<commit_after>  //////////////////////////////////////////////////////////////////////////////
/**
 * @module geo.gl
 */
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of polygonFeature
 *
 * @class
 * @returns {ggl.polygonFeature}
 */
//////////////////////////////////////////////////////////////////////////////
ggl.polygonFeature = function (arg) {
  'use strict';
  if (!(this instanceof ggl.polygonFeature)) {
    return new ggl.polygonFeature(arg);
  }
  arg = arg || {};
  geo.polygonFeature.call(this, arg);

  ////////////////////////////////////////////////////////////////////////////
  /**
   * @private
   */
  ////////////////////////////////////////////////////////////////////////////
  var m_this = this,
      m_actor = null,
      s_init = this._init,
      s_update = this._update;

  function createVertexShader() {
      var vertexShaderSource = [
        'attribute vec3 pos;',
        'attribute vec3 fillColor;',
        'attribute float fillOpacity;',
        'uniform mat4 modelViewMatrix;',
        'uniform mat4 projectionMatrix;',
        'uniform float pixelWidth;',
        'varying vec3 fillColorVar;',
        'varying float fillOpacityVar;',

        'void main(void)',
        '{',
        '  vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);',
        '  if (clipPos.w != 0.0) {',
        '    clipPos = clipPos/clipPos.w;',
        '  }',
        '  fillColorVar = fillColor;',
        '  fillOpacityVar = fillOpacity;',
        '  gl_Position = clipPos;',
        '}'
      ].join('\n'),
      shader = new vgl.shader(gl.VERTEX_SHADER);
      shader.setShaderSource(vertexShaderSource);
      return shader;
    }

  function createFragmentShader() {
    var fragmentShaderSource = [
      '#ifdef GL_ES',
      '  precision highp float;',
      '#endif',
      'varying vec3 fillColorVar;',
      'varying float fillOpacityVar;',
      'void main () {',
      '  gl_FragColor = vec4 (fillColorVar, fillOpacityVar);',
      '}'
    ].join('\n'),
    shader = new vgl.shader(gl.FRAGMENT_SHADER);
    shader.setShaderSource(fragmentShaderSource);
    return shader;
  }

  function createGLPolygons() {
    var i, numPolygons = m_this.data().length, p = null,
        numPts = null, start = null, itemIndex = 0,
        polygonItemCoordIndex = 0, position = [],
        polygonItemCoords = null, fillColor = [],
        fillOpacity = [], posFunc, fillColorFunc,
        polygonItem, fillOpacityFunc, buffers = vgl.DataBuffers(1024),
        sourcePositions = vgl.sourceDataP3fv(),
        sourceFillColor =
          vgl.sourceDataAnyfv(3, vgl.vertexAttributeKeysIndexed.Two),
        sourceFillOpacity =
          vgl.sourceDataAnyfv(1, vgl.vertexAttributeKeysIndexed.Three),
        trianglePrimitive = vgl.triangles(),
        mat = vgl.material(), blend = vgl.blend(),
        prog = vgl.shaderProgram(), vertexShader = createVertexShader(),
        fragmentShader = createFragmentShader(),
        posAttr = vgl.vertexAttribute('pos'),
        fillColorAttr = vgl.vertexAttribute('fillColor'),
        fillOpacityAttr = vgl.vertexAttribute('fillOpacity'),
        modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
        projectionUniform = new vgl.projectionUniform('projectionMatrix'),
        geom = vgl.geometryData(),
        mapper = vgl.mapper();

    posFunc = m_this.position();
    fillColorFunc = m_this.style.get('fillColor');
    fillOpacityFunc = m_this.style.get('fillOpacity');

    m_this.data().forEach(function (item) {
      var polygon = m_this.polygon()(item, itemIndex);
      polygonItem = polygon.outer;
      polygonItemCoordIndex = 0;

      var extRing = [], extIndex = 0, extLength = polygonItem.length - 1;
      extRing[0] = [];
      polygonItem.forEach(function (extRingCoords) {
        if (extIndex !== extLength) {
         //extRing = extRing.concat(extRingCoords);
         extRing[0].push({x: extRingCoords[0], y: extRingCoords[1]});
        }
        ++extIndex;
      });
      //console.log("extRing ", extRing);
      //console.log("result", PolyK.Triangulate(extRing));
      var myTriangulator = new PNLTRI.Triangulator();
      var triangList = myTriangulator.triangulate_polygon( extRing );
      var newTriangList = [];
      //var result = PolyK.Triangulate(extRing)
      //console.log(triangList);

      triangList.forEach(function (newIndices) {
        newTriangList = newTriangList.concat(newIndices);
      });

      newTriangList.forEach(function (polygonIndex) {
        polygonItemCoordIndex = polygonIndex;
        polygonItemCoords = polygonItem[polygonItemCoordIndex];

        //console.log("polygonItemData ", polygonItemCoords);
        //console.log("polygonItemData ", polygonItemCoordIndex);

        var p = posFunc(item, itemIndex,
                        polygonItemCoords, polygonItemCoordIndex);
          if (p instanceof geo.latlng) {
            position.push([p.x(), p.y(), p.z()]);
          } else {
            position.push([p.x, p.y, p.z || 0.0]);
          }
          var sc = fillColorFunc(item, itemIndex,
            polygonItemCoords, polygonItemCoordIndex);
          fillColor.push([sc.r, sc.g, sc.b]);
          fillOpacity.push(fillOpacityFunc(item,
            itemIndex, polygonItemCoords, polygonItemCoordIndex));
          polygonItemCoordIndex += 1;
        });
      itemIndex += 1;
    });

    position = geo.transform.transformCoordinates(
                 m_this.gcs(), m_this.layer().map().gcs(),
                 position, 3);

    buffers.create('pos', 3);
    buffers.create('indices', 1);
    buffers.create('fillColor', 3);
    buffers.create('fillOpacity', 1);

    numPts = position.length;

    // TODO: Right now this is ugly but we will fix it.
    prog.addVertexAttribute(posAttr,
      vgl.vertexAttributeKeys.Position);
    prog.addVertexAttribute(fillColorAttr,
      vgl.vertexAttributeKeysIndexed.Two);
    prog.addVertexAttribute(fillOpacityAttr,
      vgl.vertexAttributeKeysIndexed.Three);

    prog.addUniform(modelViewUniform);
    prog.addUniform(projectionUniform);

    prog.addShader(fragmentShader);
    prog.addShader(vertexShader);

    mat.addAttribute(prog);
    mat.addAttribute(blend);

    m_actor = vgl.actor();
    m_actor.setMaterial(mat);

    start = buffers.alloc(numPts);
    var currentIndex = start;

    //console.log("numPts ", numPts);
    for (i = 0; i < numPts; i += 1) {
      buffers.write('pos', position[i], start + i, 1);
      buffers.write('indices', [i], start + i, 1);
      buffers.write('fillColor', fillColor[i], start + i, 1);
      buffers.write('fillOpacity', [fillOpacity[i]], start + i, 1);
    }

    //console.log(buffers.get('fillColor'));
    sourcePositions.pushBack(buffers.get('pos'));
    geom.addSource(sourcePositions);

    sourceFillColor.pushBack(buffers.get('fillColor'));
    geom.addSource(sourceFillColor);

    sourceFillOpacity.pushBack(buffers.get('fillOpacity'));
    geom.addSource(sourceFillOpacity);

    //console.log(buffers.get('indices'));
    trianglePrimitive.setIndices(buffers.get('indices'));
    geom.addPrimitive(trianglePrimitive);

    mapper.setGeometryData(geom);
    m_actor.setMapper(mapper);
  }

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Initialize
   */
  ////////////////////////////////////////////////////////////////////////////
  this._init = function (arg) {
    s_init.call(m_this, arg);
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Build
   *
   * @override
   */
  ////////////////////////////////////////////////////////////////////////////
  this._build = function () {
    if (m_actor) {
      m_this.renderer().contextRenderer().removeActor(m_actor);
    }

    createGLPolygons();

    m_this.renderer().contextRenderer().addActor(m_actor);
    m_this.buildTime().modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Update
   *
   * @override
   */
  ////////////////////////////////////////////////////////////////////////////
  this._update = function () {
    s_update.call(m_this);

    if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
        m_this.updateTime().getMTime() <= m_this.getMTime()) {
      m_this._build();
    }

    m_actor.setVisible(m_this.visible());
    m_actor.material().setBinNumber(m_this.bin());
    m_this.updateTime().modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Destroy
   */
  ////////////////////////////////////////////////////////////////////////////
  this._exit = function () {
    m_this.renderer().contextRenderer().removeActor(m_actor);
  };

  this._init(arg);
  return this;
};

inherit(ggl.polygonFeature, geo.polygonFeature);

// Now register it
geo.registerFeature('vgl', 'polygon', ggl.polygonFeature);
<|endoftext|>