<commit_before>import React, { PropTypes } from 'react'
import Highlight from 'react-highlight'

import pkg from 'package.json'
import {
  Container,
  Divider,
  Grid,
  Header,
  Icon,
  Label,
  List,
  Message,
  Segment,
} from 'src'
import Logo from '../Components/Logo/Logo'

const AccordionJSX = `const panels = [{
  title: 'What is a dog?',
  content: '...',
  }, {
  title: 'What kinds are there?',
  content: '...',
}]

<Accordion panels={panels} />`
const AccordionHTML = `

    What is a dog?

...

    What kinds are there?

...

`

const ButtonJSX = `<Button size='small' color='green'>
  <Icon name='download' />
  Download
</Button>`
const ButtonHTML = `<button class="ui small green button">

  Download
</button>`
const RatingJSX = '<Rating rating={1} maxRating={5} />'
const RatingHTML = `

`

const MessageIconJSX = `<Message
  success
  icon='thumbs up'
  header='Nice job!'
  content='Your profile is complete.'
/>`
const MessageIconHTML = `

      Nice job!

    Your profile is complete.

`

const MessageSubComponentsJSX = `<Message icon>
  <Icon name='circle notched' loading />
  <Message.Content>
    <Message.Header>
      Just one second
    </Message.Header>
    We're fetching that content for you.
  </Message.Content>
</Message>`
const MessageSubComponentsHTML = `

      Just one second

    We're fetching that content for you.

`

const Comparison = ({ jsx, html }) => (
  <Segment className='code-example'>
    <Grid columns='equal' centered textAlign='left'>
      <Grid.Column mobile='16' tablet='16' computer='8' largeScreen='7'>
        JSX
        <Highlight className='language-xml'>
          {jsx}
        </Highlight>
      </Grid.Column>
      <Grid.Column only='large screen' large='1' textAlign='center'>
        <Divider vertical>
          <Icon name='right arrow circle' />
        </Divider>
      </Grid.Column>
      <Grid.Column mobile='16' tablet='16' computer='8' largeScreen='7'>
        Rendered HTML
        <Highlight className='language-html'>
          {html}
        </Highlight>
      </Grid.Column>
    </Grid>
  </Segment>
)

Comparison.propTypes = {
  jsx: PropTypes.string,
  html: PropTypes.string,
}

const Introduction = () => (
  <Container id='introduction-page'>
    <Segment className='center aligned basic'>
      <Logo centered size='small' />

        Semantic-UI-React

          {pkg.description}

    </Segment>

    <Segment className='basic padded'>
      Install
      <Message>

$ npm install {pkg.name}

      </Message>
      <List>
        <List.Item icon='check mark'>jQuery Free</List.Item>
        <List.Item icon='check mark'>Declarative Components</List.Item>
        <List.Item icon='check mark'>Shorthand Props</List.Item>
        <List.Item icon='check mark'>Sub Components</List.Item>
        <List.Item icon='check mark'>Auto Controlled State</List.Item>
      </List>
    </Segment>

    <Segment className='basic padded'>
      jQuery Free

        jQuery is a DOM manipulation library. It reads from and writes to the DOM.
        React uses a virtual DOM (a JavaScript representation of the real DOM).
        React only writes patch updates to the DOM, but never reads from it.

        It is not feasible to keep real DOM manipulations in sync with React's virtual DOM. Because of this, all
        jQuery functionality has been re-implemented in React. There is jQuery nor any direct DOM manipulation.

    </Segment>

    <Segment className='basic padded'>
      Declarative Components
      Declarative APIs provide for robust features and prop validation.

      <Comparison jsx={RatingJSX} html={RatingHTML} />
      <Comparison jsx={ButtonJSX} html={ButtonHTML} />
    </Segment>

    <Segment className='basic padded'>
      Shorthand Props

        Shorthand props generate markup for you, making many use cases a breeze.

      Child Object Arrays

        Components with repeating children accept arrays of plain objects.

           Facebook is fond of this

        over using context to handle parent-child coupling and so are we.

      <Comparison jsx={AccordionJSX} html={AccordionHTML} />

      {'icon={...}'}

        The icon prop is standard for many components.
        It can accept an Icon name, an Icon props object, or an {'<Icon />'} instance.

      <Comparison jsx={MessageIconJSX} html={MessageIconHTML} />

      {'image={...}'}

        The image prop is standard for many components.
        It can accept an image src, an Image props object, or an {'<Image />'} instance.

    </Segment>

    <Segment className='basic padded'>
      Sub Components

        Sub components give you complete access to the markup.
        This is essential for flexibility in customizing components.

      <Comparison jsx={MessageSubComponentsJSX} html={MessageSubComponentsHTML} />
    </Segment>

    <Segment className='basic padded'>
      Auto Controlled State

        React has the concept of

           controlled and uncontrolled

        components.

        Our stateful components self manage their state out of the box, without wiring. Dropdowns open on click without
        wiring onClick to the open prop. The value is also stored internally, without
        wiring onChange to value.

        If you add a value prop or an open prop, the Dropdown delegates control for that
        one prop to your value. The other props remain auto controlled. Mix and match any number of controlled and
        uncontrolled props. Add and remove control at any time by adding or removing props. Everything just works.

        See the docs for more examples.

      <Divider hidden section />
      <Divider hidden section />
      <Divider hidden section />
    </Segment>
  </Container>
)

export default Introduction
<commit_msg>feat(docs): add intro image prop example (#396)<commit_after>import React, { PropTypes } from 'react'
import Highlight from 'react-highlight'

import pkg from 'package.json'
import {
  Container,
  Divider,
  Grid,
  Header,
  Icon,
  Label,
  List,
  Message,
  Segment,
} from 'src'
import Logo from '../Components/Logo/Logo'

const AccordionJSX = `const panels = [{
  title: 'What is a dog?',
  content: '...',
  }, {
  title: 'What kinds are there?',
  content: '...',
}]

<Accordion panels={panels} />`
const AccordionHTML = `

    What is a dog?

...

    What kinds are there?

...

`

const ButtonJSX = `<Button size='small' color='green'>
  <Icon name='download' />
  Download
</Button>`
const ButtonHTML = `<button class="ui small green button">

  Download
</button>`
const LabelJSX = ""
const LabelHTML = `

`
const RatingJSX = '<Rating rating={1} maxRating={5} />'
const RatingHTML = `

`

const MessageIconJSX = `<Message
  success
  icon='thumbs up'
  header='Nice job!'
  content='Your profile is complete.'
/>`
const MessageIconHTML = `

      Nice job!

    Your profile is complete.

`

const MessageSubComponentsJSX = `<Message icon>
  <Icon name='circle notched' loading />
  <Message.Content>
    <Message.Header>
      Just one second
    </Message.Header>
    We're fetching that content for you.
  </Message.Content>
</Message>`
const MessageSubComponentsHTML = `

      Just one second

    We're fetching that content for you.

`

const Comparison = ({ jsx, html }) => (
  <Segment className='code-example'>
    <Grid columns='equal' centered textAlign='left'>
      <Grid.Column mobile='16' tablet='16' computer='8' largeScreen='7'>
        JSX
        <Highlight className='language-xml'>
          {jsx}
        </Highlight>
      </Grid.Column>
      <Grid.Column only='large screen' large='1' textAlign='center'>
        <Divider vertical>
          <Icon name='right arrow circle' />
        </Divider>
      </Grid.Column>
      <Grid.Column mobile='16' tablet='16' computer='8' largeScreen='7'>
        Rendered HTML
        <Highlight className='language-html'>
          {html}
        </Highlight>
      </Grid.Column>
    </Grid>
  </Segment>
)

Comparison.propTypes = {
  jsx: PropTypes.string,
  html: PropTypes.string,
}

const Introduction = () => (
  <Container id='introduction-page'>
    <Segment className='center aligned basic'>
      <Logo centered size='small' />

        Semantic-UI-React

          {pkg.description}

    </Segment>

    <Segment className='basic padded'>
      Install
      <Message>

$ npm install {pkg.name}

      </Message>
      <List>
        <List.Item icon='check mark'>jQuery Free</List.Item>
        <List.Item icon='check mark'>Declarative Components</List.Item>
        <List.Item icon='check mark'>Shorthand Props</List.Item>
        <List.Item icon='check mark'>Sub Components</List.Item>
        <List.Item icon='check mark'>Auto Controlled State</List.Item>
      </List>
    </Segment>

    <Segment className='basic padded'>
      jQuery Free

        jQuery is a DOM manipulation library. It reads from and writes to the DOM.
        React uses a virtual DOM (a JavaScript representation of the real DOM).
        React only writes patch updates to the DOM, but never reads from it.

        It is not feasible to keep real DOM manipulations in sync with React's virtual DOM. Because of this, all
        jQuery functionality has been re-implemented in React. There is jQuery nor any direct DOM manipulation.

    </Segment>

    <Segment className='basic padded'>
      Declarative Components
      Declarative APIs provide for robust features and prop validation.

      <Comparison jsx={RatingJSX} html={RatingHTML} />
      <Comparison jsx={ButtonJSX} html={ButtonHTML} />
    </Segment>

    <Segment className='basic padded'>
      Shorthand Props

        Shorthand props generate markup for you, making many use cases a breeze.

      Child Object Arrays

        Components with repeating children accept arrays of plain objects.

           Facebook is fond of this

        over using context to handle parent-child coupling and so are we.

      <Comparison jsx={AccordionJSX} html={AccordionHTML} />

      {'icon={...}'}

        The icon prop is standard for many components.
        It can accept an Icon name, an Icon props object, or an {'<Icon />'} instance.

      <Comparison jsx={MessageIconJSX} html={MessageIconHTML} />

      {'image={...}'}

        The image prop is standard for many components.
        It can accept an image src, an Image props object, or an {'<Image />'} instance.

      <Comparison jsx={LabelJSX} html={LabelHTML} />
    </Segment>

    <Segment className='basic padded'>
      Sub Components

        Sub components give you complete access to the markup.
        This is essential for flexibility in customizing components.

      <Comparison jsx={MessageSubComponentsJSX} html={MessageSubComponentsHTML} />
    </Segment>

    <Segment className='basic padded'>
      Auto Controlled State

        React has the concept of

           controlled and uncontrolled

        components.

        Our stateful components self manage their state out of the box, without wiring. Dropdowns open on click without
        wiring onClick to the open prop. The value is also stored internally, without
        wiring onChange to value.

        If you add a value prop or an open prop, the Dropdown delegates control for that
        one prop to your value. The other props remain auto controlled. Mix and match any number of controlled and
        uncontrolled props. Add and remove control at any time by adding or removing props. Everything just works.

        Take a look at our

           AutoControlledComponent

        to see how this was done.
        See the docs try it out live.

      <Divider hidden section />
      <Divider hidden section />
      <Divider hidden section />
    </Segment>
  </Container>
)

export default Introduction
<|endoftext|>


<commit_before>#!/usr/bin/env ringo
/**
 * @fileoverview Script to create static JsDoc documentation.
 *      Use -h or --help to get a list of available options.
 *
 * @see http://code.google.com/p/jsdoc-toolkit/
 */

// stdlib
var files = require('ringo/utils/files');
var {makeTree, write, copyTree, join, Path} = require('fs');
var {Parser} = require('ringo/args');
var {isFile, isDirectory} = require('fs');
var {ScriptRepository} = require('ringo/jsdoc');
var strings = require('ringo/utils/strings');
var objects = require('ringo/utils/objects');
var markdown = require('ringo/markdown');
var mustache = require('ringo/mustache');

var {repositoryList, moduleDoc, moduleList, structureModuleDoc, getRepositoryName}
        = require('./jsdocserializer');
var defaultContext = {};
var templates = {
        module: getResource('./templates/module.html').content,
        repository: getResource('./templates/repository.html').content,
        menu: getResource('./templates/menu.html').content,
        head: getResource('./templates/head.html').content,
        page: getResource('./templates/page.html').content
    };

/**
 * @param {Object} repostitory
 * @param {String} exportDirectory
 * @param {Boolean} quiet
 */
var renderRepository = exports.renderRepository = function (repository, exportDirectory, quiet) {
    // need apps/jsdoc on path for skin extend to work
    if (require.paths.indexOf(module.directory) == -1) {
        require.paths.push(module.directory);
    }

    if (!quiet) print ('Writing to ' + exportDirectory + '...');
    copyStaticFiles(exportDirectory);
    if (!quiet) print(repository.path);
    writeModuleList(exportDirectory, repository);
    moduleList(repository.path).forEach(function(module) {
        if (!quiet) print('\t' + module.id);
        writeModuleDoc(exportDirectory, repository, module.id);
    });

    if (!quiet) print('Finished writing to ' + exportDirectory);
    return;
}

/**
 * Copy static files of this webapp to target directory
 *
 * @param {String} target
 */
function copyStaticFiles(target) {
    makeTree(join(target, 'static'));
    copyTree(join(module.directory, 'static'), join(target, 'static'));
    return;
}

/**
 * Write the module list to directory. Corresponds to actions:repository
 *
 * @param {String} target directory of file to be written
 * @param {String} repository path
 */
function writeModuleList(target, repository) {
    var context = objects.merge(defaultContext, {
        repositoryName: repository.name,
        title: 'Module overview - ' + repository.name,
        modules: moduleList(repository.path, true),
        rootPath: './',
        markdown: function(text) {
            return markdown.process(text);
        }
    });

    context.head = mustache.to_html(templates.head, context);
    context.menu = mustache.to_html(templates.menu, context);
    context.content = mustache.to_html(templates.repository, context);
    var repositoryHtml = mustache.to_html(templates.page, context);
    write(join(target, 'index.html'), repositoryHtml);
}

/**
 * Write documentation page for a module to directory. corresponds to actions:module
 *
 * @param {String} directory
 * @param {String} repository path
 * @param {String} moduleId
 */
function writeModuleDoc(target, repository, moduleId){

    var moduleDirectory = target;
    var modules = [];
    moduleDirectory = join(target, moduleId);
    makeTree(moduleDirectory);
    modules = moduleList(repository.path);

    var docs = moduleDoc(repository.path, moduleId);
    if (docs == null) {
        throw new Error('Could not parse JsDoc for ' + repository.path + moduleId);
    }

    var slashCount = strings.count(moduleId, '/');
    var relativeRoot = '../' + strings.repeat('../', slashCount);

    var context = objects.merge(defaultContext, {
        rootPath: relativeRoot,
        repositoryName: repository.name,
        title: moduleId + ' - ' + repository.name,
        moduleId: moduleId,
        modules: modules,
        item: structureModuleDoc(docs),
        paramList: function() {
            return this.parameters.map(function(p) p.name).join(', ')
        },
        markdown: function(text) {
            return markdown.process(text);
        },
        iterate: function(value) {
            return value && value.length ? {each: value} : null;
        },
        debug: function(value) {
            print(value.toSource());
            return null;
        },
        commaList: function(value) {
            return value && value.length ? value.join(', ') : '';
        },
        newlineList: function(value) {
            return value && value.length ? value.join('
') : '';
        }
    });
    context.head = mustache.to_html(templates.head, context);
    context.menu = mustache.to_html(templates.menu, context);
    context.content = mustache.to_html(templates.module, context);
    var moduleHtml = mustache.to_html(templates.page, context);
    var moduleFile = join(moduleDirectory, 'index.html');
    write(moduleFile, moduleHtml);
}

/**
 * Create static documentation for a Repository.
 *
 *   ringo doc.js -s /home/foo/ringojs/modules/
 *
 * You can specify a human readable name for the module which will
 * be display in the documentation:
 *
 *   ringo doc.js -s /home/foo/ringojs/modules -n "Ringojs Modules"
 *
 * @param args
 */
function main(args) {

    /**
     * Print script help
     */
    function help() {
        print('Create JsDoc documentation for CommonJs modules.');
        print('Usage:');
        print('  ringo ' + script + ' -s [sourcepath]');
        print('Options:');
        print(parser.help());
        return;
    };

    var script = args.shift();
    var parser = new Parser();
    parser.addOption('s', 'source', 'repository', 'Path to repository');
    parser.addOption('d', 'directory', 'directory', 'Directory for output files (default: "out")');
    parser.addOption('t', 'template', 'file', 'Master template to use');
    parser.addOption('n', 'name', 'name', 'Name of the Repository (default: auto generated from path)');
    parser.addOption('q', 'quiet', null, 'Do not output any messages.');
    parser.addOption(null, 'file-urls', null, 'Add "index.html" to all URLs for file:// serving.');
    parser.addOption('h', 'help', null, 'Print help message and exit');

    var opts = parser.parse(args);
    if (opts.help) {
        help();
        return;
    }
    if (!opts.source) {
        throw new Error('No source specified.');
    }
    if (opts.template) {
        var t = getResource(opts.template);
        if (!t.exists()) {
            throw new Error('Template "' + opts.template + '" not found.');
        }
        templates.page = t.content;
    }

    var exportDirectory = join(opts.directory || './out/');
    var repository = {
        path: opts.source,
        name: opts.name || getRepositoryName(opts.source)
    };
    var quiet = opts.quiet || false;
    defaultContext.indexhtml = opts['fileUrls'] ? 'index.html' : '';

    // check if export dir exists & is empty
    var dest = new Path(exportDirectory);
    if (dest.exists() && !dest.isDirectory()) {
        throw new Error(dest + ' exists but is not a directory.');
    } else if (dest.isDirectory() && dest.list().length > 0) {
        throw new Error('Directory ' + dest + ' exists but is not empty');
    }

    // figure out what type of doc we write, single module, multi repos
    // or single repo
    if (!isDirectory(repository.path)) {
        throw new Error('Invalid source specified. Must be directory.');
        return;
    }

    renderRepository(repository, exportDirectory, quiet);
    return;
}

if (require.main == module) {
    try {
        main(system.args);
    } catch (error) {
        print(error);
        print('Use -h or --help to get a list of available options.');
    }
}
<commit_msg>improve jsdoc tool's jsdoc<commit_after>#!/usr/bin/env ringo
/**
 * @fileoverview Script to create static JsDoc documentation.
 *      Use -h or --help to get a list of available options.
 *
 * @see http://code.google.com/p/jsdoc-toolkit/
 */

// stdlib
var files = require('ringo/utils/files');
var {makeTree, write, copyTree, join, Path} = require('fs');
var {Parser} = require('ringo/args');
var {isFile, isDirectory} = require('fs');
var {ScriptRepository} = require('ringo/jsdoc');
var strings = require('ringo/utils/strings');
var objects = require('ringo/utils/objects');
var markdown = require('ringo/markdown');
var mustache = require('ringo/mustache');

var {repositoryList, moduleDoc, moduleList, structureModuleDoc, getRepositoryName}
        = require('./jsdocserializer');
var defaultContext = {};
var templates = {
        module: getResource('./templates/module.html').content,
        repository: getResource('./templates/repository.html').content,
        menu: getResource('./templates/menu.html').content,
        head: getResource('./templates/head.html').content,
        page: getResource('./templates/page.html').content
    };

/**
 * Renders jsdoc html files for the given repository into the exportDirectory.
 *
 * @param {Object} repository
 * @param {String} exportDirectory
 * @param {Boolean} quiet
 */
var renderRepository = exports.renderRepository = function (repository, exportDirectory, quiet) {
    // need apps/jsdoc on path for skin extend to work
    if (require.paths.indexOf(module.directory) == -1) {
        require.paths.push(module.directory);
    }

    if (!quiet) print ('Writing to ' + exportDirectory + '...');
    copyStaticFiles(exportDirectory);
    if (!quiet) print(repository.path);
    writeModuleList(exportDirectory, repository);
    moduleList(repository.path).forEach(function(module) {
        if (!quiet) print('\t' + module.id);
        writeModuleDoc(exportDirectory, repository, module.id);
    });

    if (!quiet) print('Finished writing to ' + exportDirectory);
    return;
}

/**
 * Copy static files of this webapp to target directory
 *
 * @param {String} target
 */
function copyStaticFiles(target) {
    makeTree(join(target, 'static'));
    copyTree(join(module.directory, 'static'), join(target, 'static'));
    return;
}

/**
 * Write the html file listing all modules to directory.
 *
 * @param {String} target directory of html file to be written
 * @param {String} repository path
 */
function writeModuleList(target, repository) {
    var context = objects.merge(defaultContext, {
        repositoryName: repository.name,
        title: 'Module overview - ' + repository.name,
        modules: moduleList(repository.path, true),
        rootPath: './',
        markdown: function(text) {
            return markdown.process(text);
        }
    });

    context.head = mustache.to_html(templates.head, context);
    context.menu = mustache.to_html(templates.menu, context);
    context.content = mustache.to_html(templates.repository, context);
    var repositoryHtml = mustache.to_html(templates.page, context);
    write(join(target, 'index.html'), repositoryHtml);
}

/**
 * Write html page documenting one module to the directory.
 *
 * @param {String} directory
 * @param {String} repository path
 * @param {String} moduleId
 */
function writeModuleDoc(target, repository, moduleId){

    var moduleDirectory = target;
    var modules = [];
    moduleDirectory = join(target, moduleId);
    makeTree(moduleDirectory);
    modules = moduleList(repository.path);

    var docs = moduleDoc(repository.path, moduleId);
    if (docs == null) {
        throw new Error('Could not parse JsDoc for ' + repository.path + moduleId);
    }

    var slashCount = strings.count(moduleId, '/');
    var relativeRoot = '../' + strings.repeat('../', slashCount);

    var context = objects.merge(defaultContext, {
        rootPath: relativeRoot,
        repositoryName: repository.name,
        title: moduleId + ' - ' + repository.name,
        moduleId: moduleId,
        modules: modules,
        item: structureModuleDoc(docs),
        paramList: function() {
            return this.parameters.map(function(p) p.name).join(', ')
        },
        markdown: function(text) {
            return markdown.process(text);
        },
        iterate: function(value) {
            return value && value.length ? {each: value} : null;
        },
        debug: function(value) {
            print(value.toSource());
            return null;
        },
        commaList: function(value) {
            return value && value.length ? value.join(', ') : '';
        },
        newlineList: function(value) {
            return value && value.length ? value.join('
') : '';
        }
    });
    context.head = mustache.to_html(templates.head, context);
    context.menu = mustache.to_html(templates.menu, context);
    context.content = mustache.to_html(templates.module, context);
    var moduleHtml = mustache.to_html(templates.page, context);
    var moduleFile = join(moduleDirectory, 'index.html');
    write(moduleFile, moduleHtml);
}

/**
 * Create static documentation for a Repository.
 *
 *   ringo-doc -s /home/foo/ringojs/modules/
 *
 * You can specify a human readable name for the module which will
 * be display in the documentation:
 *
 *   ringo-doc -s /home/foo/ringojs/modules -n "Ringojs Modules"
 *
 * @param args
 */
function main(args) {

    /**
     * Print script help
     */
    function help() {
        print('Create JsDoc documentation for CommonJs modules.');
        print('Usage:');
        print('  ringo ' + script + ' -s [sourcepath]');
        print('Options:');
        print(parser.help());
        return;
    };

    var script = args.shift();
    var parser = new Parser();
    parser.addOption('s', 'source', 'repository', 'Path to repository');
    parser.addOption('d', 'directory', 'directory', 'Directory for output files (default: "out")');
    parser.addOption('t', 'template', 'file', 'Master template to use');
    parser.addOption('n', 'name', 'name', 'Name of the Repository (default: auto generated from path)');
    parser.addOption('q', 'quiet', null, 'Do not output any messages.');
    parser.addOption(null, 'file-urls', null, 'Add "index.html" to all URLs for file:// serving.');
    parser.addOption('h', 'help', null, 'Print help message and exit');

    var opts = parser.parse(args);
    if (opts.help) {
        help();
        return;
    }
    if (!opts.source) {
        throw new Error('No source specified.');
    }
    if (opts.template) {
        var t = getResource(opts.template);
        if (!t.exists()) {
            throw new Error('Template "' + opts.template + '" not found.');
        }
        templates.page = t.content;
    }

    var exportDirectory = join(opts.directory || './out/');
    var repository = {
        path: opts.source,
        name: opts.name || getRepositoryName(opts.source)
    };
    var quiet = opts.quiet || false;
    defaultContext.indexhtml = opts['fileUrls'] ? 'index.html' : '';

    // check if export dir exists & is empty
    var dest = new Path(exportDirectory);
    if (dest.exists() && !dest.isDirectory()) {
        throw new Error(dest + ' exists but is not a directory.');
    } else if (dest.isDirectory() && dest.list().length > 0) {
        throw new Error('Directory ' + dest + ' exists but is not empty');
    }

    if (!isDirectory(repository.path)) {
        throw new Error('Invalid source specified. Must be directory.');
        return;
    }

    renderRepository(repository, exportDirectory, quiet);
    return;
}

if (require.main == module) {
    try {
        main(system.args);
    } catch (error) {
        print(error);
        print('Use -h or --help to get a list of available options.');
    }
}
<|endoftext|>


<commit_before>(function () {
  'use strict';
  angular
    .module('polls')
    .controller('PollsListController', PollsListController);

  PollsListController.$inject = [
    '$scope',
    '$window',
    'Socket',
    'Authentication',
    'Action',
    'toasty',
    'toastr'
  ];

  function PollsListController(
    $scope,
    $window,
    Socket,
    Authentication,
    Action,
    toasty,
    toast
  ) {
    var vm = this;
    vm.authentication = Authentication;
    vm.isLogged = vm.authentication.user ? true : false;
    vm.polls = [];
    vm.hot_polls = [];
    vm.categorys = [];
    vm.new_data = [];
    vm.page = 0;
    vm.busy = false;
    vm.stopped = false;
    vm.get_polls = get_polls;
    vm.new_polls = [];
    init();

    function init() {
      initSocket();
      loadCategorys();
      // load Hot poll
      loadHotPolls();
    }

    function initSocket() {
      if (!Socket.socket) {
        Socket.connect();
      }
      Socket.emit('subscribe_public');
      Socket.on('poll_create', pollId => {
        vm.new_polls.push(pollId);
      });
      $scope.$on('$destroy', function () {
        Socket.emit('unsubscribe_public');
        Socket.removeListener('poll_create');
      });
    }

    function get_polls() {
      if (vm.stopped || vm.busy) return;
      vm.busy = true;
      Action.get_polls(vm.page)
        .then(res => {
          if (!res.data.length || res.data.length === 0) {
            vm.stopped = true;
            vm.busy = false;
            return;
          }
          // Load options và tính vote cho các opt trong polls
          vm.new_data = res.data || [];
          var promises = [];
          vm.new_data.forEach(poll => {
            poll.isCurrentUserOwner = vm.isLogged && vm.authentication.user._id === poll.user._id;
            promises.push(get_poll_report(poll));
            promises.push(get_opts(poll));
            promises.push(get_owner_follow(poll));
            promises.push(get_reported(poll));
            promises.push(get_bookmarked(poll));
          });
          return Promise.all(promises);
        })
        .then(res => {
          // Gán data vào list hiện tại
          vm.polls = _.union(vm.polls, vm.new_data);
          vm.page += 1;
          vm.busy = false;
          $scope.$apply();
          vm.new_data = [];
        })
        .catch(err => {
          vm.busy = false;
          vm.stopped = true;
          toast.error(err.message, 'Error!');
        });
    }
    function get_poll_report(poll) {
      return new Promise((resolve, reject) => {
        Action.get_poll_report(poll._id)
          .then(res => {
            poll.report = res.data;
            return resolve(res);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_opts(poll) {
      return new Promise((resolve, reject) => {
        Action.get_opts(poll._id)
          .then(res => {
            poll.opts = _.where(res.data, { status: 1 }) || [];
            return get_vote_for_poll(poll);
          })
          .then(res => {
            return resolve(poll);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_vote_for_poll(poll) {
      return new Promise((resolve, reject) => {
        Action.get_voteopts(poll._id)
          .then(res => {
            poll.chart = {
              options: { responsive: true },
              colors: [],
              labels: [],
              data: []
            };
            poll.votes = res.data.votes || [];
            poll.voteopts = res.data.voteopts || [];
            poll.total = poll.voteopts.length;
            poll.opts.forEach(opt => {
              opt.voteCnt = _.where(poll.voteopts, { opt: opt._id }).length || 0;
              opt.progressVal = calPercen(poll.total, opt.voteCnt);
              poll.chart.data.push(opt.voteCnt);
              poll.chart.colors.push(opt.color);
              poll.chart.labels.push(opt.title);
            });
            return resolve(poll);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function loadHotPolls() {
      Action.get_hot_polls(0)
        .then(res => {
          vm.hot_polls = res.data || [];
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    }
    function loadCategorys() {
      Action.get_categorys()
        .then(res => {
          vm.categorys = res;
        });
    }
    function get_owner_follow(poll) {
      return new Promise((resolve, reject) => {
        if (!vm.isLogged) {
          poll.follow = {};
          return resolve();
        }
        Action.get_follow(poll._id)
          .then(res => {
            poll.follow = res.data || { poll: poll._id };
            return resolve(res.data);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_reported(poll) {
      return new Promise((resolve, reject) => {
        if (!vm.isLogged) {
          poll.reported = false;
          return resolve();
        }
        Action.get_report(poll._id)
          .then(res => {
            poll.reported = (res.data) ? res.data : false;
            return resolve(res.data);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_bookmarked(poll) {
      return new Promise((resolve, reject) => {
        if (!vm.isLogged) {
          poll.bookmarked = false;
          return resolve();
        }
        Action.get_bookmark(poll._id)
          .then(res => {
            poll.bookmarked = (res.data) ? res.data : false;
            return resolve(res.data);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    // Tính phần trăm tỉ lệ vote cho opt
    function calPercen(total, value) {
      if (total === 0) {
        return 0;
      }
      return Math.floor(value * 100 / total) || 0;
    }
    // Thao tác khác
    vm.delete_poll = (poll) => {
      if (!poll.isCurrentUserOwner) {
        toast.error('You are not authorized.', 'Error!');
        return;
      }
      if ($window.confirm('Are you sure you want to delete?')) {
        vm.polls = _.without(vm.polls, poll);
        Action.delete_poll(poll);
      }
    };
    vm.report_poll = (poll) => {
      if (poll.reported) {
        toast.error('You are already report this poll.', 'Error!');
        return;
      }
      Action.save_report(poll._id)
        .then(res => {
          poll.reported = (res) ? true : false;
          $scope.$apply();
          toast.success('You have successfully reported this poll.', 'Thank you!');
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    };
    vm.bookmark_poll = (poll) => {
      if (poll.bookmarked) {
        toast.error('You are already bookmark this poll.', 'Error!');
        return;
      }
      Action.save_bookmark(poll._id)
        .then(res => {
          poll.bookmarked = (res) ? true : false;
          $scope.$apply();
          toast.success('Added to bookmarks.', 'Success!');
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    };
    vm.follow_poll = (poll) => {
      if (!vm.isLogged) {
        toast.error('You must login to follow this poll.', 'Error!');
        return;
      }
      Action.save_follow(poll.follow)
        .then(res => {
          poll.follow = res;
          $scope.$apply();
          toast.success('You followed this poll.', 'Success!');
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    };
    vm.load_new = () => {
      console.log(vm.polls);
      var tmp_list = _.clone(vm.new_polls);
      vm.new_polls = [];
      angular.forEach(tmp_list, (item, index) => {
        var _poll = {};
        Action.get_poll_http(item)
          .then(res => {
            _poll = res.data || {};
            var promises = [];
            _poll.isCurrentUserOwner = vm.isLogged && vm.authentication.user._id === _poll.user._id;
            promises.push(get_poll_report(_poll));
            promises.push(get_opts(_poll));
            promises.push(get_owner_follow(_poll));
            promises.push(get_reported(_poll));
            promises.push(get_bookmarked(_poll));
            return Promise.all(promises);
          })
          .then(poll => {
            console.log(poll);
            console.log(_poll);
            if (_poll._id) {
              vm.polls.push(_poll);
              console.log('Add new poll to polls');
            }
            console.log(vm.polls);
          })
          .catch(err => {
            toast.error(err.message, 'Error!');
          });
      });
    };
    vm.themes = [{
      name: 'Default Theme',
      code: 'default'
    }, {
      name: 'Material Design',
      code: 'material'
    }, {
      name: 'Bootstrap 3',
      code: 'bootstrap'
    }];
    vm.toastyTypes = [{
      name: 'Default',
      code: 'default',
    }, {
      name: 'Info',
      code: 'info'
    }, {
      name: 'Success',
      code: 'success'
    }, {
      name: 'Wait',
      code: 'wait'
    }, {
      name: 'Error',
      code: 'error'
    }, {
      name: 'Warning',
      code: 'warning'
    }];
    vm.toastyOpts = {
      title: 'Toast It!',
      msg: 'Mmmm, tasties...',
      showClose: false,
      clickToClose: true,
      timeout: 5000,
      sound: true,
      html: false,
      shake: false,
      theme: vm.themes[1].code,
      type: vm.toastyTypes[0].code
    };
    vm.show_toasty = () => {
      toasty[vm.toastyOpts.type]({
        title: vm.toastyOpts.title,
        msg: vm.toastyOpts.msg,
        showClose: vm.toastyOpts.showClose,
        clickToClose: vm.toastyOpts.clickToClose,
        sound: vm.toastyOpts.sound,
        shake: vm.toastyOpts.shake,
        timeout: vm.toastyOpts.timeout || false,
        html: vm.toastyOpts.html,
        theme: vm.toastyOpts.theme
      });
    };
    vm.show_toastr = () => {
      toast.success('Hello world!', 'Toastr fun!');
      toast.error('Hello world!', 'Toastr fun!');
      toast.info('Hello world!', 'Toastr fun!');
      toast.wait('Hello world!', 'Toastr fun!');
    };
  }
})();
<commit_msg>Commited from HaNhatLenh at Fri, Jul 07, 2017  6:19:22 PM<commit_after>(function () {
  'use strict';
  angular
    .module('polls')
    .controller('PollsListController', PollsListController);

  PollsListController.$inject = [
    '$scope',
    '$window',
    'Socket',
    'Authentication',
    'Action',
    'toasty',
    'toastr'
  ];

  function PollsListController(
    $scope,
    $window,
    Socket,
    Authentication,
    Action,
    toasty,
    toast
  ) {
    var vm = this;
    vm.authentication = Authentication;
    vm.isLogged = vm.authentication.user ? true : false;
    vm.polls = [];
    vm.hot_polls = [];
    vm.categorys = [];
    vm.new_data = [];
    vm.page = 0;
    vm.busy = false;
    vm.stopped = false;
    vm.get_polls = get_polls;
    vm.new_polls = [];
    init();

    function init() {
      initSocket();
      loadCategorys();
      // load Hot poll
      loadHotPolls();
    }

    function initSocket() {
      if (!Socket.socket) {
        Socket.connect();
      }
      Socket.emit('subscribe_public');
      Socket.on('poll_create', pollId => {
        vm.new_polls.push(pollId);
      });
      $scope.$on('$destroy', function () {
        Socket.emit('unsubscribe_public');
        Socket.removeListener('poll_create');
      });
    }

    function get_polls() {
      if (vm.stopped || vm.busy) return;
      vm.busy = true;
      Action.get_polls(vm.page)
        .then(res => {
          if (!res.data.length || res.data.length === 0) {
            vm.stopped = true;
            vm.busy = false;
            return;
          }
          // Load options và tính vote cho các opt trong polls
          vm.new_data = res.data || [];
          var promises = [];
          vm.new_data.forEach(poll => {
            poll.isCurrentUserOwner = vm.isLogged && vm.authentication.user._id === poll.user._id;
            promises.push(get_poll_report(poll));
            promises.push(get_opts(poll));
            promises.push(get_owner_follow(poll));
            promises.push(get_reported(poll));
            promises.push(get_bookmarked(poll));
          });
          return Promise.all(promises);
        })
        .then(res => {
          // Gán data vào list hiện tại
          vm.polls = _.union(vm.polls, vm.new_data);
          vm.page += 1;
          vm.busy = false;
          $scope.$apply();
          vm.new_data = [];
        })
        .catch(err => {
          vm.busy = false;
          vm.stopped = true;
          toast.error(err.message, 'Error!');
        });
    }
    function get_poll_report(poll) {
      return new Promise((resolve, reject) => {
        Action.get_poll_report(poll._id)
          .then(res => {
            poll.report = res.data;
            return resolve(res);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_opts(poll) {
      return new Promise((resolve, reject) => {
        Action.get_opts(poll._id)
          .then(res => {
            poll.opts = _.where(res.data, { status: 1 }) || [];
            return get_vote_for_poll(poll);
          })
          .then(res => {
            return resolve(poll);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_vote_for_poll(poll) {
      return new Promise((resolve, reject) => {
        Action.get_voteopts(poll._id)
          .then(res => {
            poll.chart = {
              options: { responsive: true },
              colors: [],
              labels: [],
              data: []
            };
            poll.votes = res.data.votes || [];
            poll.voteopts = res.data.voteopts || [];
            poll.total = poll.voteopts.length;
            poll.opts.forEach(opt => {
              opt.voteCnt = _.where(poll.voteopts, { opt: opt._id }).length || 0;
              opt.progressVal = calPercen(poll.total, opt.voteCnt);
              poll.chart.data.push(opt.voteCnt);
              poll.chart.colors.push(opt.color);
              poll.chart.labels.push(opt.title);
            });
            return resolve(poll);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function loadHotPolls() {
      Action.get_hot_polls(0)
        .then(res => {
          vm.hot_polls = res.data || [];
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    }
    function loadCategorys() {
      Action.get_categorys()
        .then(res => {
          vm.categorys = res;
        });
    }
    function get_owner_follow(poll) {
      return new Promise((resolve, reject) => {
        if (!vm.isLogged) {
          poll.follow = {};
          return resolve();
        }
        Action.get_follow(poll._id)
          .then(res => {
            poll.follow = res.data || { poll: poll._id };
            return resolve(res.data);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_reported(poll) {
      return new Promise((resolve, reject) => {
        if (!vm.isLogged) {
          poll.reported = false;
          return resolve();
        }
        Action.get_report(poll._id)
          .then(res => {
            poll.reported = (res.data) ? res.data : false;
            return resolve(res.data);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    function get_bookmarked(poll) {
      return new Promise((resolve, reject) => {
        if (!vm.isLogged) {
          poll.bookmarked = false;
          return resolve();
        }
        Action.get_bookmark(poll._id)
          .then(res => {
            poll.bookmarked = (res.data) ? res.data : false;
            return resolve(res.data);
          })
          .catch(err => {
            return reject(err);
          });
      });
    }
    // Tính phần trăm tỉ lệ vote cho opt
    function calPercen(total, value) {
      if (total === 0) {
        return 0;
      }
      return Math.floor(value * 100 / total) || 0;
    }
    // Thao tác khác
    vm.delete_poll = (poll) => {
      if (!poll.isCurrentUserOwner) {
        toast.error('You are not authorized.', 'Error!');
        return;
      }
      if ($window.confirm('Are you sure you want to delete?')) {
        vm.polls = _.without(vm.polls, poll);
        Action.delete_poll(poll);
      }
    };
    vm.report_poll = (poll) => {
      if (poll.reported) {
        toast.error('You are already report this poll.', 'Error!');
        return;
      }
      Action.save_report(poll._id)
        .then(res => {
          poll.reported = (res) ? true : false;
          $scope.$apply();
          toast.success('You have successfully reported this poll.', 'Thank you!');
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    };
    vm.bookmark_poll = (poll) => {
      if (poll.bookmarked) {
        toast.error('You are already bookmark this poll.', 'Error!');
        return;
      }
      Action.save_bookmark(poll._id)
        .then(res => {
          poll.bookmarked = (res) ? true : false;
          $scope.$apply();
          toast.success('Added to bookmarks.', 'Success!');
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    };
    vm.follow_poll = (poll) => {
      if (!vm.isLogged) {
        toast.error('You must login to follow this poll.', 'Error!');
        return;
      }
      Action.save_follow(poll.follow)
        .then(res => {
          poll.follow = res;
          $scope.$apply();
          toast.success('You followed this poll.', 'Success!');
        })
        .catch(err => {
          toast.error(err.message, 'Error!');
        });
    };
    vm.load_new = () => {
      var tmp_list = _.clone(vm.new_polls);
      vm.new_polls = [];
      angular.forEach(tmp_list, (item, index) => {
        var _poll = {};
        Action.get_poll_http(item)
          .then(res => {
            _poll = res.data || {};
            var promises = [];
            _poll.isCurrentUserOwner = vm.isLogged && vm.authentication.user._id === _poll.user._id;
            promises.push(get_poll_report(_poll));
            promises.push(get_opts(_poll));
            promises.push(get_owner_follow(_poll));
            promises.push(get_reported(_poll));
            promises.push(get_bookmarked(_poll));
            return Promise.all(promises);
          })
          .then(poll => {
            if (_poll._id) {
              vm.polls.push(_poll);
            }
          })
          .catch(err => {
            toast.error(err.message, 'Error!');
          });
      });
      $scope.$apply();
    };
    vm.themes = [{
      name: 'Default Theme',
      code: 'default'
    }, {
      name: 'Material Design',
      code: 'material'
    }, {
      name: 'Bootstrap 3',
      code: 'bootstrap'
    }];
    vm.toastyTypes = [{
      name: 'Default',
      code: 'default',
    }, {
      name: 'Info',
      code: 'info'
    }, {
      name: 'Success',
      code: 'success'
    }, {
      name: 'Wait',
      code: 'wait'
    }, {
      name: 'Error',
      code: 'error'
    }, {
      name: 'Warning',
      code: 'warning'
    }];
    vm.toastyOpts = {
      title: 'Toast It!',
      msg: 'Mmmm, tasties...',
      showClose: false,
      clickToClose: true,
      timeout: 5000,
      sound: true,
      html: false,
      shake: false,
      theme: vm.themes[1].code,
      type: vm.toastyTypes[0].code
    };
    vm.show_toasty = () => {
      toasty[vm.toastyOpts.type]({
        title: vm.toastyOpts.title,
        msg: vm.toastyOpts.msg,
        showClose: vm.toastyOpts.showClose,
        clickToClose: vm.toastyOpts.clickToClose,
        sound: vm.toastyOpts.sound,
        shake: vm.toastyOpts.shake,
        timeout: vm.toastyOpts.timeout || false,
        html: vm.toastyOpts.html,
        theme: vm.toastyOpts.theme
      });
    };
    vm.show_toastr = () => {
      toast.success('Hello world!', 'Toastr fun!');
      toast.error('Hello world!', 'Toastr fun!');
      toast.info('Hello world!', 'Toastr fun!');
      toast.wait('Hello world!', 'Toastr fun!');
    };
  }
})();
<|endoftext|>


<commit_before>/*jshint eqeqeq:false */

var set = Ember.set, get = Ember.get, getPath = Ember.getPath;
var indexOf = Ember.ArrayUtils.indexOf, indexesOf = Ember.ArrayUtils.indexesOf;

/**
  @class

  The Ember.Select view class renders a
  [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
  allowing the user to choose from a list of options. The selected option(s)
  are updated live in the `selection` property.

  @extends Ember.View
*/
Ember.Select = Ember.View.extend(
  /** @scope Ember.Select.prototype */ {

  tagName: 'select',
  defaultTemplate: Ember.Handlebars.compile('{{#if view.prompt}}<option>{{view.prompt}}</option>{{/if}}{{#each view.content}}{{view Ember.SelectOption contentBinding="this"}}{{/each}}'),
  attributeBindings: ['multiple'],

  /**
    The `multiple` attribute of the select element. Indicates whether multiple
    options can be selected.

    @type Boolean
    @default false
  */
  multiple: false,

  /**
    The list of options.

    If `optionLabelPath` and `optionValuePath` are not overridden, this should
    be a list of strings, which will serve simultaneously as labels and values.

    Otherwise, this should be a list of objects. For instance:

        content: Ember.A([
            { id: 1, firstName: 'Yehuda' },
            { id: 2, firstName: 'Tom' }
          ])),
        optionLabelPath: 'content.firstName',
        optionValuePath: 'content.id'

    @type Array
    @default null
  */
  content: null,

  /**
    When `multiple` is false, the element of `content` that is currently
    selected, if any.

    When `multiple` is true, an array of such elements.

    @type Object or Array
    @default null
  */
  selection: null,

  /**
    In single selection mode (when `multiple` is false), value can be used to get
    the current selection's value or set the selection by it's value.

    It is not currently supported in multiple selection mode.

    @type String
    @default null
  */
  value: Ember.computed(function(key, value) {
    if (arguments.length === 2) { return value; }

    var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
    return valuePath ? getPath(this, 'selection.' + valuePath) : get(this, 'selection');
  }).property('selection').cacheable(),

  /**
    If given, a top-most dummy option will be rendered to serve as a user
    prompt.

    @type String
    @default null
  */
  prompt: null,

  /**
    The path of the option labels. See `content`.

    @type String
    @default 'content'
  */
  optionLabelPath: 'content',

  /**
    The path of the option values. See `content`.

    @type String
    @default 'content'
  */
  optionValuePath: 'content',

  change: function() {
    if (get(this, 'multiple')) {
      this._changeMultiple();
    } else {
      this._changeSingle();
    }
  },

  selectionDidChange: Ember.observer(function() {
    var selection = get(this, 'selection'),
        isArray = Ember.isArray(selection);
    if (get(this, 'multiple')) {
      if (!isArray) {
        set(this, 'selection', Ember.A([selection]));
        return;
      }
      this._selectionDidChangeMultiple();
    } else {
      this._selectionDidChangeSingle();
    }
  }, 'selection'),

  valueDidChange: Ember.observer(function() {
    var content = get(this, 'content'),
        value = get(this, 'value'),
        valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
        selectedValue = (valuePath ? getPath(this, 'selection.' + valuePath) : get(this, 'selection')),
        selection;

    if (value !== selectedValue) {
      selection = content.find(function(obj) {
        return value === (valuePath ? get(obj, valuePath) : obj);
      });

      this.set('selection', selection);
    }
  }, 'value'),

  _triggerChange: function() {
    var selection = get(this, 'selection');

    if (selection) { this.selectionDidChange(); }

    this.change();
  },

  _changeSingle: function() {
    var selectedIndex = this.$()[0].selectedIndex,
        content = get(this, 'content'),
        prompt = get(this, 'prompt');

    if (!content) { return; }
    if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }

    if (prompt) { selectedIndex -= 1; }
    set(this, 'selection', content.objectAt(selectedIndex));
  },

  _changeMultiple: function() {
    var options = this.$('option:selected'),
        prompt = get(this, 'prompt'),
        offset = prompt ? 1 : 0,
        content = get(this, 'content');

    if (!content){ return; }
    if (options) {
      var selectedIndexes = options.map(function(){
        return this.index - offset;
      }).toArray();
      set(this, 'selection', content.objectsAt(selectedIndexes));
    }
  },

  _selectionDidChangeSingle: function() {
    var el = this.$()[0],
        content = get(this, 'content'),
        selection = get(this, 'selection'),
        selectionIndex = content ? indexOf(content, selection) : -1,
        prompt = get(this, 'prompt');

    if (prompt) { selectionIndex += 1; }
    if (el) { el.selectedIndex = selectionIndex; }
  },

  _selectionDidChangeMultiple: function() {
    var content = get(this, 'content'),
        selection = get(this, 'selection'),
        selectedIndexes = content ? indexesOf(content, selection) : [-1],
        prompt = get(this, 'prompt'),
        offset = prompt ? 1 : 0,
        options = this.$('option'),
        adjusted;

    if (options) {
      options.each(function() {
        adjusted = this.index > -1 ? this.index + offset : -1;
        this.selected = indexOf(selectedIndexes, adjusted) > -1;
      });
    }
  },

  init: function() {
    this._super();
    this.on("didInsertElement", this, this._triggerChange);
  }

});

Ember.SelectOption = Ember.View.extend({
  tagName: 'option',
  defaultTemplate: Ember.Handlebars.compile("{{view.label}}"),
  attributeBindings: ['value', 'selected'],

  init: function() {
    this.labelPathDidChange();
    this.valuePathDidChange();

    this._super();
  },

  selected: Ember.computed(function() {
    var content = get(this, 'content'),
        selection = getPath(this, 'parentView.selection');
    if (getPath(this, 'parentView.multiple')) {
      return selection && indexOf(selection, content) > -1;
    } else {
      // Primitives get passed through bindings as objects... since
      // `new Number(4) !== 4`, we use `==` below
      return content == selection;
    }
  }).property('content', 'parentView.selection').volatile(),

  labelPathDidChange: Ember.observer(function() {
    var labelPath = getPath(this, 'parentView.optionLabelPath');

    if (!labelPath) { return; }

    Ember.defineProperty(this, 'label', Ember.computed(function() {
      return getPath(this, labelPath);
    }).property(labelPath).cacheable());
  }, 'parentView.optionLabelPath'),

  valuePathDidChange: Ember.observer(function() {
    var valuePath = getPath(this, 'parentView.optionValuePath');

    if (!valuePath) { return; }

    Ember.defineProperty(this, 'value', Ember.computed(function() {
      return getPath(this, valuePath);
    }).property(valuePath).cacheable());
  }, 'parentView.optionValuePath')
});

<commit_msg>Added documentation to Ember.Select<commit_after>/*jshint eqeqeq:false */

var set = Ember.set, get = Ember.get, getPath = Ember.getPath;
var indexOf = Ember.ArrayUtils.indexOf, indexesOf = Ember.ArrayUtils.indexesOf;

/**
  @class

  The Ember.Select view class renders a
  [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
  allowing the user to choose from a list of options. The selected option(s)
  are updated live in the `selection` property, while the corresponding value
  is updated in the `value` property.

  ### Using Strings
  The simplest version of an Ember.Select takes an array of strings for the options
  of a select box and a valueBinding to set the value.

  Example:

      App.controller = Ember.Object.create({
        selected: null,
        content: [
          "Yehuda",
          "Tom"
        ]
      })

      {{view Ember.Select
             contentBinding="App.controller.content"
             valueBinding="App.controller.selected"
      }}

  Would result in the following HTML:

      <select class="ember-select">
        <option value="Yehuda">Yehuda</option>
        <option value="Tom">Tom</option>
      </select>

  Selecting Yehuda from the select box will set `App.controller.selected` to "Yehuda"

  ### Using Objects
  An Ember.Select can also take an array of JS or Ember objects.

  When using objects you need to supply optionLabelPath and optionValuePath parameters
  which will be used to get the label and value for each of the options.

  Usually you will bind to either the selection or the value attribute of the select.

  Use selectionBinding if you would like to set the whole object as a property on the target.
  Use valueBinding if you would like to set just the value.

  Example using selectionBinding:

      App.controller = Ember.Object.create({
        selectedPerson: null,
        selectedPersonId: null,
        content: [
          Ember.Object.create({firstName: "Yehuda", id: 1}),
          Ember.Object.create({firstName: "Tom",    id: 2})
        ]
      })

      {{view Ember.Select
             contentBinding="App.controller.content"
             optionLabelPath="content.firstName"
             optionValuePath="content.id"
             selectionBinding="App.controller.selectedPerson"
             prompt="Please Select"}}

      <select class="ember-select">
        <option value>Please Select</option>
        <option value="1">Yehuda</option>
        <option value="2">Tom</option>
      </select>

  Selecting Yehuda here will set `App.controller.selectedPerson` to
  the Yehuda object.

  Example using valueBinding:

      {{view Ember.Select
             contentBinding="App.controller.content"
             optionLabelPath="content.firstName"
             optionValuePath="content.id"
             valueBinding="App.controller.selectedPersonId"
             prompt="Please Select"}}

  Selecting Yehuda in this case will set `App.controller.selectedPersonId` to 1.

  @extends Ember.View
*/
Ember.Select = Ember.View.extend(
  /** @scope Ember.Select.prototype */ {

  tagName: 'select',
  defaultTemplate: Ember.Handlebars.compile('{{#if view.prompt}}<option>{{view.prompt}}</option>{{/if}}{{#each view.content}}{{view Ember.SelectOption contentBinding="this"}}{{/each}}'),
  attributeBindings: ['multiple'],

  /**
    The `multiple` attribute of the select element. Indicates whether multiple
    options can be selected.

    @type Boolean
    @default false
  */
  multiple: false,

  /**
    The list of options.

    If `optionLabelPath` and `optionValuePath` are not overridden, this should
    be a list of strings, which will serve simultaneously as labels and values.

    Otherwise, this should be a list of objects. For instance:

        content: Ember.A([
            { id: 1, firstName: 'Yehuda' },
            { id: 2, firstName: 'Tom' }
          ])),
        optionLabelPath: 'content.firstName',
        optionValuePath: 'content.id'

    @type Array
    @default null
  */
  content: null,

  /**
    When `multiple` is false, the element of `content` that is currently
    selected, if any.

    When `multiple` is true, an array of such elements.

    @type Object or Array
    @default null
  */
  selection: null,

  /**
    In single selection mode (when `multiple` is false), value can be used to get
    the current selection's value or set the selection by it's value.

    It is not currently supported in multiple selection mode.

    @type String
    @default null
  */
  value: Ember.computed(function(key, value) {
    if (arguments.length === 2) { return value; }

    var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
    return valuePath ? getPath(this, 'selection.' + valuePath) : get(this, 'selection');
  }).property('selection').cacheable(),

  /**
    If given, a top-most dummy option will be rendered to serve as a user
    prompt.

    @type String
    @default null
  */
  prompt: null,

  /**
    The path of the option labels. See `content`.

    @type String
    @default 'content'
  */
  optionLabelPath: 'content',

  /**
    The path of the option values. See `content`.

    @type String
    @default 'content'
  */
  optionValuePath: 'content',

  change: function() {
    if (get(this, 'multiple')) {
      this._changeMultiple();
    } else {
      this._changeSingle();
    }
  },

  selectionDidChange: Ember.observer(function() {
    var selection = get(this, 'selection'),
        isArray = Ember.isArray(selection);
    if (get(this, 'multiple')) {
      if (!isArray) {
        set(this, 'selection', Ember.A([selection]));
        return;
      }
      this._selectionDidChangeMultiple();
    } else {
      this._selectionDidChangeSingle();
    }
  }, 'selection'),

  valueDidChange: Ember.observer(function() {
    var content = get(this, 'content'),
        value = get(this, 'value'),
        valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
        selectedValue = (valuePath ? getPath(this, 'selection.' + valuePath) : get(this, 'selection')),
        selection;

    if (value !== selectedValue) {
      selection = content.find(function(obj) {
        return value === (valuePath ? get(obj, valuePath) : obj);
      });

      this.set('selection', selection);
    }
  }, 'value'),

  _triggerChange: function() {
    var selection = get(this, 'selection');

    if (selection) { this.selectionDidChange(); }

    this.change();
  },

  _changeSingle: function() {
    var selectedIndex = this.$()[0].selectedIndex,
        content = get(this, 'content'),
        prompt = get(this, 'prompt');

    if (!content) { return; }
    if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }

    if (prompt) { selectedIndex -= 1; }
    set(this, 'selection', content.objectAt(selectedIndex));
  },

  _changeMultiple: function() {
    var options = this.$('option:selected'),
        prompt = get(this, 'prompt'),
        offset = prompt ? 1 : 0,
        content = get(this, 'content');

    if (!content){ return; }
    if (options) {
      var selectedIndexes = options.map(function(){
        return this.index - offset;
      }).toArray();
      set(this, 'selection', content.objectsAt(selectedIndexes));
    }
  },

  _selectionDidChangeSingle: function() {
    var el = this.$()[0],
        content = get(this, 'content'),
        selection = get(this, 'selection'),
        selectionIndex = content ? indexOf(content, selection) : -1,
        prompt = get(this, 'prompt');

    if (prompt) { selectionIndex += 1; }
    if (el) { el.selectedIndex = selectionIndex; }
  },

  _selectionDidChangeMultiple: function() {
    var content = get(this, 'content'),
        selection = get(this, 'selection'),
        selectedIndexes = content ? indexesOf(content, selection) : [-1],
        prompt = get(this, 'prompt'),
        offset = prompt ? 1 : 0,
        options = this.$('option'),
        adjusted;

    if (options) {
      options.each(function() {
        adjusted = this.index > -1 ? this.index + offset : -1;
        this.selected = indexOf(selectedIndexes, adjusted) > -1;
      });
    }
  },

  init: function() {
    this._super();
    this.on("didInsertElement", this, this._triggerChange);
  }

});

Ember.SelectOption = Ember.View.extend({
  tagName: 'option',
  defaultTemplate: Ember.Handlebars.compile("{{view.label}}"),
  attributeBindings: ['value', 'selected'],

  init: function() {
    this.labelPathDidChange();
    this.valuePathDidChange();

    this._super();
  },

  selected: Ember.computed(function() {
    var content = get(this, 'content'),
        selection = getPath(this, 'parentView.selection');
    if (getPath(this, 'parentView.multiple')) {
      return selection && indexOf(selection, content) > -1;
    } else {
      // Primitives get passed through bindings as objects... since
      // `new Number(4) !== 4`, we use `==` below
      return content == selection;
    }
  }).property('content', 'parentView.selection').volatile(),

  labelPathDidChange: Ember.observer(function() {
    var labelPath = getPath(this, 'parentView.optionLabelPath');

    if (!labelPath) { return; }

    Ember.defineProperty(this, 'label', Ember.computed(function() {
      return getPath(this, labelPath);
    }).property(labelPath).cacheable());
  }, 'parentView.optionLabelPath'),

  valuePathDidChange: Ember.observer(function() {
    var valuePath = getPath(this, 'parentView.optionValuePath');

    if (!valuePath) { return; }

    Ember.defineProperty(this, 'value', Ember.computed(function() {
      return getPath(this, valuePath);
    }).property(valuePath).cacheable());
  }, 'parentView.optionValuePath')
});

<|endoftext|>


<commit_before>/*
 * Tree view control
 *
 * Author   : Mikhail Shiryaev
 * Created  : 2016
 * Modified : 2016
 */

/*
 * Requires:
 * - jquery
 * - scadautils.js
 */

// Rapid SCADA namespace
var scada = scada || {};

// Tree views processing object
scada.treeView = {
    // Expand or collapse tree node
    _toggleTreeNode: function (divExpander) {
        var treeNode = divExpander.parent().parent();
        var childNodes = treeNode.next(".child-nodes");

        if (divExpander.hasClass("expanded")) {
            divExpander.removeClass("expanded");
            childNodes.css("display", "none");
        } else {
            divExpander.addClass("expanded");
            childNodes.css("display", "block");
        }
    },

    // Expand selected tree node
    _expandSelectedTreeNode: function (allNodes) {
        var selNodes = allNodes.filter(".selected");
        var parDivs = selNodes.parentsUntil(".tree-view", ".child-nodes");

        parDivs.prev(".node").find(".expander").addClass("expanded");
        parDivs.css("display", "block");
    },

    // Tune tree view elements and bind events
    prepare: function () {
        var treeViews = $(".tree-view");
        var oneIndent = treeViews.find(".indent:first").width();
        var allNodes = treeViews.find(".node");

        // set width of indents according to their level
        allNodes.each(function () {
            var level = $(this).attr("data-level");
            $(this).find(".indent").width(oneIndent * level);
        });

        // expand selected tree node
        this._expandSelectedTreeNode(allNodes);

        // go to link or toggle tree node on click
        var thisTreeView = this;
        allNodes
        .off()
        .click(function () {
            var link = $(this).find("a");

            if (link.length > 0) {
                scada.utils.clickLink(link);
            } else {
                var expander = $(this).find(".expander");
                if (!expander.hasClass("empty")) {
                    thisTreeView._toggleTreeNode(expander);
                }
            }
        });
    }
};<commit_msg>ScadaWeb5: select view node<commit_after>/*
 * Tree view control
 *
 * Author   : Mikhail Shiryaev
 * Created  : 2016
 * Modified : 2016
 */

/*
 * Requires:
 * - jquery
 * - scadautils.js
 */

// Rapid SCADA namespace
var scada = scada || {};

// Tree views processing object
scada.treeView = {
    // Expand or collapse tree node
    _toggleTreeNode: function (divExpander) {
        var treeNode = divExpander.parent().parent();
        var childNodes = treeNode.next(".child-nodes");

        if (divExpander.hasClass("expanded")) {
            divExpander.removeClass("expanded");
            childNodes.css("display", "none");
        } else {
            divExpander.addClass("expanded");
            childNodes.css("display", "block");
        }
    },

    // Expand selected tree node
    _expandSelectedTreeNode: function (allNodes) {
        var selNodes = allNodes.filter(".selected");
        var parDivs = selNodes.parentsUntil(".tree-view", ".child-nodes");

        parDivs.prev(".node").find(".expander").addClass("expanded");
        parDivs.css("display", "block");
    },

    // Tune tree view elements and bind events
    prepare: function () {
        var treeViews = $(".tree-view");
        var oneIndent = treeViews.find(".indent:first").width();
        var allNodes = treeViews.find(".node");

        // set width of indents according to their level
        allNodes.each(function () {
            var level = $(this).attr("data-level");
            $(this).find(".indent").width(oneIndent * level);
        });

        // expand selected tree node
        this._expandSelectedTreeNode(allNodes);

        // go to link or toggle tree node on click
        var thisTreeView = this;
        allNodes
        .off()
        .click(function () {
            var node = $(this);
            var link = node.find("a");

            if (link.length > 0) {
                allNodes.removeClass("selected");
                node.addClass("selected");
                scada.utils.clickLink(link);
            } else {
                var expander = node.find(".expander");
                if (!expander.hasClass("empty")) {
                    thisTreeView._toggleTreeNode(expander);
                }
            }
        });
    }
};<|endoftext|>


<commit_before>
$( document ).ready(function() {
  var big_number = {
    "total": 273,
    "bold_description": "Marijuana facilities that are currently running in the City and County of Denver.",
  };

  facilitiesSource("http://data.denvergov.org/dataset/city-and-county-of-denver-marijuana-facilities");
  facilitiesCount(big_number);
  facilitiesDonut();
});

function facilitiesSource(url) {
  $("#facilities-source").attr('href', url);
}

function facilitiesCount(big_number) {
  $("#facilities-number").text(big_number.total);
  $("#facilities-number-strong").text(big_number.bold_description);
  $("#facilities-number-description").text(big_number.description);
}

function facilitiesDonut() {
    var svg = dimple.newSvg("#facilities-donut", '100%', '100%');
    d3.csv("data/facilities-licenses.csv", function (data) {
      var myChart = new dimple.chart(svg, data);
      myChart.addMeasureAxis("p", "value");
      var ring = myChart.addSeries("label", dimple.plot.pie);
      ring.getTooltipText = function (e) {
        return [ e.aggField[0], formatPercent(e.piePct) ];
      };

      // Styling: Change this Kavi!
      ring.innerRadius = "50%";
      myChart.defaultColors = [
        new dimple.color("#4dddff"),
        new dimple.color("#b3eaf4")
      ];
      myChart.addLegend(0,20,"50%",10,"left");
      myChart.draw();
    });
}

// Closure
(function(){

  /**
   * Decimal adjustment of a number.
   *
   * @param {String}  type  The type of adjustment.
   * @param {Number}  value The number.
   * @param {Integer} exp   The exponent (the 10 logarithm of the adjustment base).
   * @returns {Number}      The adjusted value.
   */
  function decimalAdjust(type, value, exp) {
    // If the exp is undefined or zero...
    if (typeof exp === 'undefined' || +exp === 0) {
      return Math[type](value);
    }
    value = +value;
    exp = +exp;
    // If the value is not a number or the exp is not an integer...
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
      return NaN;
    }
    // Shift
    value = value.toString().split('e');
    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
    // Shift back
    value = value.toString().split('e');
    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
  }

  // Decimal round
  if (!Math.round10) {
    Math.round10 = function(value, exp) {
      return decimalAdjust('round', value, exp);
    };
  }
  // Decimal floor
  if (!Math.floor10) {
    Math.floor10 = function(value, exp) {
      return decimalAdjust('floor', value, exp);
    };
  }
  // Decimal ceil
  if (!Math.ceil10) {
    Math.ceil10 = function(value, exp) {
      return decimalAdjust('ceil', value, exp);
    };
  }

})();

function formatPercent(number){
  return (Math.round10(number * 100, -1)) + '%';
}<commit_msg>re-adds legend<commit_after>
$( document ).ready(function() {
  var big_number = {
    "total": 273,
    "bold_description": "Marijuana facilities that are currently running in the City and County of Denver.",
  };

  facilitiesSource("http://data.denvergov.org/dataset/city-and-county-of-denver-marijuana-facilities");
  facilitiesCount(big_number);
  facilitiesDonut();
});

function facilitiesSource(url) {
  $("#facilities-source").attr('href', url);
}

function facilitiesCount(big_number) {
  $("#facilities-number").text(big_number.total);
  $("#facilities-number-strong").text(big_number.bold_description);
  $("#facilities-number-description").text(big_number.description);
}

function facilitiesDonut() {
  var svg = dimple.newSvg("#facilities-donut", '100%', '100%');
  d3.csv("data/facilities-licenses.csv", function (data) {
    var myChart = new dimple.chart(svg, data);
    myChart.addMeasureAxis("p", "value");
    var ring = myChart.addSeries("label", dimple.plot.pie);

    // Tooltip
    ring.getTooltipText = function (e) {
      return [ e.aggField[0], formatPercent(e.piePct) ];
    };

    ring.innerRadius = "50%";
    myChart.defaultColors = [
      new dimple.color("#4dddff"),
      new dimple.color("#b3eaf4")
    ];
    myChart.addLegend("0",20,"50%","100%","left");
    myChart.draw();
  });
}

// Closure
(function(){

  /**
   * Decimal adjustment of a number.
   *
   * @param {String}  type  The type of adjustment.
   * @param {Number}  value The number.
   * @param {Integer} exp   The exponent (the 10 logarithm of the adjustment base).
   * @returns {Number}      The adjusted value.
   */
  function decimalAdjust(type, value, exp) {
    // If the exp is undefined or zero...
    if (typeof exp === 'undefined' || +exp === 0) {
      return Math[type](value);
    }
    value = +value;
    exp = +exp;
    // If the value is not a number or the exp is not an integer...
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
      return NaN;
    }
    // Shift
    value = value.toString().split('e');
    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
    // Shift back
    value = value.toString().split('e');
    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
  }

  // Decimal round
  if (!Math.round10) {
    Math.round10 = function(value, exp) {
      return decimalAdjust('round', value, exp);
    };
  }
  // Decimal floor
  if (!Math.floor10) {
    Math.floor10 = function(value, exp) {
      return decimalAdjust('floor', value, exp);
    };
  }
  // Decimal ceil
  if (!Math.ceil10) {
    Math.ceil10 = function(value, exp) {
      return decimalAdjust('ceil', value, exp);
    };
  }

})();

function formatPercent(number){
  return (Math.round10(number * 100, -1)) + '%';
}<|endoftext|>


<commit_before>
var _gaq = _gaq || [];  // asynchronous stack used by google analytics

openerp.web_analytics = function(instance) {

    /*
    *  The Web Analytics Module inserts the Google Analytics JS Snippet
    *  at the top of the page, and sends to google an url each time the
    *  openerp url is changed.
    *  The pushes of the urls is made by triggering the 'state_pushed' event in the
    *  web_client.do_push_state() method which is responsible of changing the openerp current url
    */

    // Google Analytics Code snippet
    (function() {
        var ga   = document.createElement('script');
        ga.type  = 'text/javascript';
        ga.async = true;
        ga.src   = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(ga,s);
    })();

    instance.web_analytics.Tracker = instance.web.Class.extend({
        /*
        *  This method initializes the tracker
        */
        init: function() {
            /* Comment this lines when going on production, only used for testing on localhost */
            _gaq.push(['_setAccount', 'UA-35793871-1']);
            _gaq.push(['_setDomainName', 'none']);
            /**/

            /* Uncomment this lines when going on production
            _gaq.push(['_setAccount', 'UA-7333765-1']);
            _gaq.push(['_setDomainName', '.openerp.com']);  // Allow multi-domain
            */
        },
        /*
        * This method MUST be overriden by saas_demo and saas_trial in order to
        * set the correct user type. By default, the user connected is local to the DB.
        */
        _get_user_type: function() {
            return 'Local User';
        },
        _set_user_access_level: function() {
            if (instance.session.uid === 1) {
                return 'Admin User';
            // Make the difference between portal users and anonymous users
            } else if (instance.session.username.indexOf('@') !== -1) {
                if (instance.session.username.indexOf('anonymous') === -1) {
                    return 'Portal User';
                } else {
                    return 'Anonymous User';
                }
            } else if (instance.session.username.indexOf('anonymous') !== -1) {
                return 'Anonymous User';
            } else {
                return 'Normal User';
            }
        },
        /*
        * This method contains the initialization of all user-related custom variables
        * stored in GA. Also other modules can override it to add new custom variables
        */
        initialize_custom: function() {
            // Track User Access Level, Custom Variable 4 in GA with visitor level scope
            // Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User'
            _gaq.push(['_setCustomVar', 4, 'User Access Level', this.user_access_level, 1]);

            // Track User Type Conversion, Custom Variable 3 in GA with session level scope
            // Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
            _gaq.push(['_setCustomVar', 1, 'User Type Conversion', this._get_user_type(), 2]);

            return instance.session.rpc("/web/webclient/version_info", {})
                .done(function(res) {
                    _gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]);
                    return;
                });
        },
        /*
        *  This method contains the initialization of the object and view type
        *  custom variables stored in GA. Setting here the CV will work if web_analytics
        *  module is auto-install and not anonymous user, because the first view
        *  of a fresh DB is the modules kanban view (also in trial), otherwise the
        *  responsibility of the first initialization relies on
        *  instance.web.ActionManager#ir_actions_client method
        */
        on_state_pushed: function(state) {
            // Track only pages corresponding to a 'normal' view of OpenERP, views
            // related to client actions are tracked by the action manager
            if (state.model && state.view_type) {
                // Track object usage
                _gaq.push(['_setCustomVar', 2, 'Object', state.model, 3]);
                // Tack view usage
                _gaq.push(['_setCustomVar', 3, 'View Type', state.view_type, 3]);
                // Track the page
                var url = instance.web_analytics.generateUrl({'model': state.model, 'view_type': state.view_type});
                _gaq.push(['_trackPageview', url]);
            }
        },
        /*
        * This method includes the tracker into view and managers. It can be overriden
        * by other modules in order to extend tracking functionalities
        */
        include_tracker: function() {
            // Track the events related with the creation and the  modification of records,
            // the view type is always form
            instance.web.FormView = instance.web.FormView.extend({
                init: function(parent, dataset, view_id, options) {
                    this._super.apply(this, arguments);
                    var self = this;
                    this.on('record_created', self, function(r) {
                        var url = instance.web_analytics.generateUrl({'model': this.model, 'view_type': 'form'});
                        _gaq.push(['_trackEvent', this.model, 'on_button_create_save', url]);
                    });
                    this.on('record_saved', self, function(r) {
                        var url = instance.web_analytics.generateUrl({'model': this.model, 'view_type': 'form'});
                        _gaq.push(['_trackEvent', this.model, 'on_button_edit_save', url]);
                    });
                }
            });

            // Track client actions, also if not in a fresh DB or anonymous user,
            // it initializes the CV related to objects and view types
            instance.web.ActionManager.include({
                ir_actions_client: function (action, options) {
                    // Try to set the correct model, else it will be 'ir.actions.client'
                    if (action.res_model) {
                        _gaq.push(['_setCustomVar', 2, 'Object', action.res_model, 3]);
                    } else {
                        _gaq.push(['_setCustomVar', 2, 'Object', action.type, 3]);
                    }
                    // Try to set a view type as accurate as possible
                    if (action.name) {
                        _gaq.push(['_setCustomVar', 3, 'View Type', action.name, 3]);
                    } else {
                        _gaq.push(['_setCustomVar', 3, 'View Type', action.tag, 3]);
                    }
                    var url = instance.web_analytics.generateUrl({'action': action.tag});
                    _gaq.push(['_trackPageview', url]);
                    return this._super.apply(this, arguments);
                },
            });

            // Track button events
            instance.web.View.include({
                do_execute_action: function(action_data, dataset, record_id, on_closed) {
                    var category = this.model || dataset.model || '';
                    var action;
                    if (action_data.name && _.isNaN(action_data.name-0)) {
                        action = action_data.name;
                    } else {
                        action = action_data.string || action_data.special || '';
                    }
                    var label = instance.web_analytics.generateUrl({'model': category, 'view_type': this.view_type});
                    _gaq.push(['_trackEvent', category, action, label]);
                    return this._super.apply(this, arguments);
                },
            });

            // Track error events
            instance.web.CrashManager.include({
                show_error: function(error) {
                    var hash = window.location.hash;
                    var params = $.deparam(hash.substr(hash.indexOf('#')+1));
                    var options = {};
                    if (params.model && params.view_type) {
                        options = {'model': params.model, 'view_type': params.view_type};
                    } else {
                        options = {'action': params.action};
                    }
                    var label = instance.web_analytics.generateUrl(options);
                    if (error.code) {
                        _gaq.push(['_trackEvent', error.message, error.data.fault_code, label, ,true]);
                    } else {
                        _gaq.push(['_trackEvent', error.type, error.data.debug, label, ,true]);
                    }
                    this._super.apply(this, arguments);
                },
            });
        },
    });

    // Set correctly the tracker in the current instance
    if (instance.client instanceof instance.web.WebClient) {        // not for embedded clients
        instance.webclient.tracker = new instance.web_analytics.Tracker();
        $.when(instance.webclient.tracker._set_user_access_level()).then(function(r) {
            instance.webclient.tracker.user_access_level = r;
            instance.webclient.tracker.initialize_custom().then(function() {
                instance.webclient.on('state_pushed', self, instance.webclient.tracker.on_state_pushed);
                instance.webclient.tracker.include_tracker();
            });
        });
    } else if (!instance.client) {
        // client does not already exists, we are in monodb mode
        instance.web.WebClient.include({
            start: function() {
                var d = this._super.apply(this, arguments);
                this.tracker = new instance.web_analytics.Tracker();
                return d;
            },
            show_application: function() {
                var self = this;
                $.when(this.tracker._set_user_access_level()).then(function(r) {
                    self.tracker.user_access_level = r;
                    self.tracker.initialize_custom().then(function() {
                        instance.webclient.on('state_pushed', self, self.tracker.on_state_pushed);
                        self.tracker.include_tracker();
                    });
                    self._super();
                });
            },
        });
    }

    // ----------------------------------------------------------------
    // utility functions
    // ----------------------------------------------------------------

    instance.web_analytics.generateUrl = function(options) {
        var url = '';
        _.each(options, function(value, key) {
            url += '/' + key + '=' + value;
        });
        return url;
    };

};
<commit_msg>[IMP] Refactoring setup on webclient, changed Object and View Type custom variables onto events (GA doesn't support multiple sets on page-level cv)<commit_after>
var _gaq = _gaq || [];  // asynchronous stack used by google analytics

openerp.web_analytics = function(instance) {

    /*
    *  The Web Analytics Module inserts the Google Analytics JS Snippet
    *  at the top of the page, and sends to google an url each time the
    *  openerp url is changed.
    *  The pushes of the urls is made by triggering the 'state_pushed' event in the
    *  web_client.do_push_state() method which is responsible of changing the openerp current url
    */

    // Google Analytics Code snippet
    (function() {
        var ga   = document.createElement('script');
        ga.type  = 'text/javascript';
        ga.async = true;
        ga.src   = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(ga,s);
    })();

    instance.web_analytics.Tracker = instance.web.Class.extend({
        /*
        *  This method initializes the tracker
        */
        init: function() {
            /* Comment this lines when going on production, only used for testing on localhost */
            _gaq.push(['_setAccount', 'UA-35793871-1']);
            _gaq.push(['_setDomainName', 'none']);
            /**/

            /* Uncomment this lines when going on production
            _gaq.push(['_setAccount', 'UA-7333765-1']);
            _gaq.push(['_setDomainName', '.openerp.com']);  // Allow multi-domain
            */
        },
        /*
        * This method MUST be overriden by saas_demo and saas_trial in order to
        * set the correct user type. By default, the user connected is local to the DB.
        */
        _get_user_type: function() {
            return 'Local User';
        },
        _set_user_access_level: function() {
            if (instance.session.uid === 1) {
                return 'Admin User';
            // Make the difference between portal users and anonymous users
            } else if (instance.session.username.indexOf('@') !== -1) {
                if (instance.session.username.indexOf('anonymous') === -1) {
                    return 'Portal User';
                } else {
                    return 'Anonymous User';
                }
            } else if (instance.session.username.indexOf('anonymous') !== -1) {
                return 'Anonymous User';
            } else {
                return 'Normal User';
            }
        },
        /*
        * This method contains the initialization of all user-related custom variables
        * stored in GA. Also other modules can override it to add new custom variables
        */
        initialize_custom: function() {
            // Track User Access Level, Custom Variable 4 in GA with visitor level scope
            // Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User'
            _gaq.push(['_setCustomVar', 4, 'User Access Level', this.user_access_level, 1]);

            // Track User Type Conversion, Custom Variable 3 in GA with session level scope
            // Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
            _gaq.push(['_setCustomVar', 1, 'User Type Conversion', this._get_user_type(), 2]);

            return instance.session.rpc("/web/webclient/version_info", {})
                .done(function(res) {
                    _gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]);
                    return;
                });
        },
        /*
        *  This method contains the initialization of the object and view type
        *  as an event in GA.
        */
        on_state_pushed: function(state) {
            // Track only pages corresponding to a 'normal' view of OpenERP, views
            // related to client actions are tracked by the action manager
            if (state.model && state.view_type) {
                // Track the page
                var label = instance.web_analytics.generateUrl({'model': state.model, 'view_type': state.view_type});
                _gaq.push(['_trackEvent', state.model, state.view_type, label]);
            }
        },
        /*
        * This method includes the tracker into view and managers. It can be overriden
        * by other modules in order to extend tracking functionalities
        */
        include_tracker: function() {
            // Track the events related with the creation and the  modification of records,
            // the view type is always form
            instance.web.FormView.include({
                init: function(parent, dataset, view_id, options) {
                    this._super.apply(this, arguments);
                    var self = this;
                    this.on('record_created', self, function(r) {
                        var url = instance.web_analytics.generateUrl({'model': this.model, 'view_type': 'form'});
                        _gaq.push(['_trackEvent', this.model, 'on_button_create_save', url]);
                    });
                    this.on('record_saved', self, function(r) {
                        var url = instance.web_analytics.generateUrl({'model': this.model, 'view_type': 'form'});
                        _gaq.push(['_trackEvent', this.model, 'on_button_edit_save', url]);
                    });
                }
            });

            // Track client actions
            instance.web.ActionManager.include({
                ir_actions_client: function (action, options) {
                    var label = instance.web_analytics.generateUrl({'action': action.tag});
                    var category = action.res_model || action.type;
                    var ga_action = action.name || action.tag;
                    _gaq.push(['_trackEvent', category, ga_action, label]);
                    return this._super.apply(this, arguments);
                },
            });

            // Track button events
            instance.web.View.include({
                do_execute_action: function(action_data, dataset, record_id, on_closed) {
                    var category = this.model || dataset.model || '';
                    var action;
                    if (action_data.name && _.isNaN(action_data.name-0)) {
                        action = action_data.name;
                    } else {
                        action = action_data.string || action_data.special || '';
                    }
                    var label = instance.web_analytics.generateUrl({'model': category, 'view_type': this.view_type});
                    _gaq.push(['_trackEvent', category, action, label]);
                    return this._super.apply(this, arguments);
                },
            });

            // Track error events
            instance.web.CrashManager.include({
                show_error: function(error) {
                    var hash = window.location.hash;
                    var params = $.deparam(hash.substr(hash.indexOf('#')+1));
                    var options = {};
                    if (params.model && params.view_type) {
                        options = {'model': params.model, 'view_type': params.view_type};
                    } else {
                        options = {'action': params.action};
                    }
                    var label = instance.web_analytics.generateUrl(options);
                    if (error.code) {
                        _gaq.push(['_trackEvent', error.message, error.data.fault_code, label, ,true]);
                    } else {
                        _gaq.push(['_trackEvent', error.type, error.data.debug, label, ,true]);
                    }
                    this._super.apply(this, arguments);
                },
            });
        },
    });

    // ----------------------------------------------------------------
    // utility functions
    // ----------------------------------------------------------------

    instance.web_analytics.generateUrl = function(options) {
        var url = '';
        _.each(options, function(value, key) {
            url += '/' + key + '=' + value;
        });
        return url;
    };

    instance.web_analytics.setupTracker = function(wc) {
        var t = wc.tracker;
        return $.when(t._set_user_access_level()).then(function(r) {
            t.user_access_level = r;
            t.initialize_custom().then(function() {
                wc.on('state_pushed', wc, t.on_state_pushed);
                t.include_tracker();
            });
        });
    };

    // Set correctly the tracker in the current instance
    if (instance.client instanceof instance.web.WebClient) {        // not for embedded clients
        instance.webclient.tracker = new instance.web_analytics.Tracker();
        instance.web_analytics.setupTracker(instance.webclient);
    } else if (!instance.client) {
        // client does not already exists, we are in monodb mode
        instance.web.WebClient.include({
            start: function() {
                var d = this._super.apply(this, arguments);
                this.tracker = new instance.web_analytics.Tracker();
                return d;
            },
            show_application: function() {
                var self = this;
                instance.web_analytics.setupTracker(self).then(function() {
                    self._super();
                });
            },
        });
    }

};
<|endoftext|>