<commit_before>import React from 'react';
import { shallow } from 'enzyme';
import GeneratedOutput from '../src/components/GeneratedOutput';
import en from '../src/i18n/en';

describe('GeneratedOutput', () => {
  let wrapper;
  const context = { language: en };

  describe('is not generated', () => {
    beforeEach(() => {
      wrapper = shallow(<GeneratedOutput isGenerated headCode="" bodyCode="" />, { context });
    });

    it('should be defined', () => {
      expect(wrapper).toBeDefined();
    });

    it('should not render', () => {
      expect(wrapper.find('textarea').length).toBe(0);
    });
  });

  describe('is generated', () => {
    it('should render', () => {
      expect(wrapper.find('textarea').length).toBe(2);
    });
  });
});
<commit_msg>:white_check_mark: added test spec for displaying headCode and bodyCode<commit_after>import React from 'react';
import { shallow } from 'enzyme';
import GeneratedOutput from '../src/components/GeneratedOutput';
import en from '../src/i18n/en';

describe('GeneratedOutput', () => {
  let wrapper;
  const context = { language: en };
  const headCode = 'head code';
  const bodyCode = 'body code';

  describe('is not generated', () => {
    beforeEach(() => {
      wrapper = shallow(<GeneratedOutput
        headCode={headCode}
        bodyCode={bodyCode}
        isGenerated={false}
      />, { context });
    });

    it('should be defined', () => {
      expect(wrapper).toBeDefined();
    });

    it('should not render', () => {
      expect(wrapper.find('textarea').length).toBe(0);
    });
  });

  describe('is generated', () => {
    beforeEach(() => {
      wrapper = shallow(<GeneratedOutput
        headCode={headCode}
        bodyCode={bodyCode}
        isGenerated
      />, { context });
    });

    it('should render', () => {
      expect(wrapper.find('textarea').length).toBe(2);
    });

    it('should display headCode', () => {
      const input = wrapper.find('textarea').first();
      expect(input.props().defaultValue).toBe(headCode);
    });

    it('should display bodyCode', () => {
      const input = wrapper.find('textarea').at(1);
      expect(input.props().defaultValue).toBe(bodyCode);
    });
  });
});
<|endoftext|>


<commit_before>import React, { render, useState } from 'preact/compat';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import { act } from 'preact/test-utils';

describe('Textarea', () => {
	let scratch;

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should alias value to children', () => {
		render(<textarea value="foo" />, scratch);

		expect(scratch.innerHTML).to.equal('<textarea>foo</textarea>');
	});

	it('should alias defaultValue to children', () => {
		render(<textarea defaultValue="foo" />, scratch);

		expect(scratch.innerHTML).to.equal('<textarea>foo</textarea>');
	});

	it('should support resetting the value', () => {
		let set;
		const App = () => {
			const [state, setState] = useState('');
			set = setState;
			return <textarea value={state} />;
		};

		render(<App />, scratch);
		expect(scratch.innerHTML).to.equal('<textarea></textarea>');

		act(() => {
			set('hello');
		});
		expect(scratch.innerHTML).to.equal('<textarea>hello</textarea>');

		act(() => {
			set('');
		});
		expect(scratch.innerHTML).to.equal('<textarea></textarea>');
	});
});
<commit_msg>Update compat/test/browser/textarea.test.js<commit_after>import React, { render, useState } from 'preact/compat';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import { act } from 'preact/test-utils';

describe('Textarea', () => {
	let scratch;

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should alias value to children', () => {
		render(<textarea value="foo" />, scratch);

		expect(scratch.innerHTML).to.equal('<textarea>foo</textarea>');
	});

	it('should alias defaultValue to children', () => {
		render(<textarea defaultValue="foo" />, scratch);

		expect(scratch.innerHTML).to.equal('<textarea>foo</textarea>');
	});

	it('should support resetting the value', () => {
		let set;
		const App = () => {
			const [state, setState] = useState('');
			set = setState;
			return <textarea value={state} />;
		};

		render(<App />, scratch);
		expect(scratch.innerHTML).to.equal('<textarea></textarea>');

		act(() => {
			set('hello');
		});
		expect(scratch.firstElementChild.value).to.equal('hello');
		expect(scratch.innerHTML).to.equal('<textarea>hello</textarea>');

		act(() => {
			set('');
		});
		expect(scratch.innerHTML).to.equal('<textarea></textarea>');
	});
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/surikata/01.jpg'),
  require('../../images/animals/surikata/02.jpg'),
  require('../../images/animals/surikata/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/surikata/01-thumb.jpg'),
  require('../../images/animals/surikata/02-thumb.jpg'),
  require('../../images/animals/surikata/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Surikata, která v češtině nemá druhé jméno a odborný název ji odbyl pojmenováním <Text style={styles.italic}>Suricata suricatta</Text>, je šelma nepříliš velkého vzrůstu, měří zhruba 30 centimetrů, k tomu má ocas dlouhý 20 centimetrů a váží necelé kilo. Kromě toho se vyznačuje výskytem na západě jihu Afriky, v polopouštích a suchých stepích, kde se baví mimo jiné hloubením nor, ve kterých žijí početné surikatí klany. Do podzemí se ale schovávají jen před nebezpečím nebo při velkém žáru, surikaty se rády sluní venku.
        </AnimalText>
        <InPageImage indexes={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Jedna nora může mít klidně 90 východů a plochu tunelů v řádu stovek metrů čtverečních. To je číslo vzhledem k velikosti průměrné surikaty obdivuhodné, práci jim ovšem usnadňuje, že mají k hrabání uzpůsobené drápy, díky nimž dokážou za vteřinu vyházet tolik písku, co samy váží. Navíc v jedné kolonii surikat žije 10–30 jedinců, tedy 2 či 3 rodiny včetně mláďat.
        </AnimalText>
        <AnimalText>
          Surikatám se obvykle rodí čtyři potomci, po 11týdenní březosti se o ně nestará pouze matka, ale celý kolektiv, surikaty mají v podstatě chůvy. První tři týdny života jsou malá surikaťátka v noře, později už smí ven, kde jsou pod dohledem dospělých. Nad celým surikatím hemžením vždycky stojí aspoň jedna hlídka, která upozorňuje ostatní na nebezpečí vyluzováním různě bizarních zvuků. Surikaty samy loví převážně bezobratlé, příležitostně i plazy a drobné ptáky nebo savce.
        </AnimalText>
        <InPageImage indexes={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Na závěr uveďme ještě dvě evoluční výhody surikat. Jednak mají na ušním boltci záhyb, který zamezuje tomu, aby jim do uší lezl písek a další nečistoty, jednak jsou značně odolné proti jedu – vydrží uštknutí hadem nebo štírem, které by zabilo i člověka.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<commit_msg>Update surikata.adult.js<commit_after>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/surikata/01.jpg'),
  require('../../images/animals/surikata/02.jpg'),
  require('../../images/animals/surikata/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/surikata/01-thumb.jpg'),
  require('../../images/animals/surikata/02-thumb.jpg'),
  require('../../images/animals/surikata/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Surikata, která v češtině nemá druhé jméno a odborný název ji odbyl pojmenováním <Text style={styles.italic}>Suricata suricatta</Text>, je šelma nepříliš velkého vzrůstu, měří zhruba 30 centimetrů, k tomu má ocas dlouhý 20 centimetrů a váží necelé kilo. Kromě toho se vyznačuje výskytem na západě jihu Afriky, v polopouštích a suchých stepích, kde se baví mimo jiné hloubením nor, ve kterých žijí početné surikatí klany. Do podzemí se ale schovává jen před nebezpečím nebo při velkém žáru, jinak se ráda sluní venku.
        </AnimalText>
        <InPageImage indexes={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Jedna nora může mít klidně 90 východů a plochu tunelů v řádu stovek metrů čtverečních. To je číslo vzhledem k velikosti průměrné surikaty obdivuhodné, práci jí ovšem usnadňuje, že má k hrabání uzpůsobené drápy, díky nimž dokáže za vteřinu vyházet tolik písku, kolik sama váží. Navíc v jedné kolonii surikat žije 10–30 jedinců, tedy 2 či 3 rodiny včetně mláďat.
        </AnimalText>
        <AnimalText>
          Surikatám se obvykle rodí čtyři potomci, po 11týdenní březosti se o ně nestará pouze matka, ale celý kolektiv, surikaty mají v podstatě chůvy. První tři týdny života jsou malá surikaťátka v noře, později už smí ven, kde jsou pod dohledem dospělých. Nad celým surikatím hemžením vždycky stojí aspoň jedna hlídka, která upozorňuje ostatní na nebezpečí vyluzováním různě bizarních zvuků. Surikaty samy loví převážně bezobratlé, příležitostně i plazy a drobné ptáky nebo savce.
        </AnimalText>
        <InPageImage indexes={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Na závěr uveďme ještě dvě evoluční výhody surikat. Jednak mají na ušním boltci záhyb, který zamezuje tomu, aby jim do uší lezl písek a další nečistoty, jednak jsou značně odolné proti jedu – vydrží uštknutí hadem nebo štírem, které by zabilo i člověka.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<|endoftext|>


<commit_before>#!/usr/bin/env node

// maintainer note - update this manually when doing new releases:
var version = '0.25.3'

var fs = require('fs')
var os = require('os')
var path = require('path')
var extract = require('extract-zip')
var download = require('electron-download')

var platform = os.platform()

function onerror (err) {
  throw err
}

var paths = {
  darwin: path.join(__dirname, './dist/Electron.app/Contents/MacOS/Electron'),
  linux: path.join(__dirname, './dist/electron'),
  win32: path.join(__dirname, './dist/electron.exe')
}

if (!paths[platform]) throw new Error('Unknown platform: ' + platform)

// downloads if not cached
download({version: version}, extractFile)

// unzips and makes path.txt point at the correct executable
function extractFile (err, zipPath) {
  if (err) return onerror(err)
  fs.writeFile(path.join(__dirname, 'path.txt'), paths[platform], function (err) {
    if (err) return onerror(err)
    extract(zipPath, {dir: path.join(__dirname, 'dist')}, function (err) {
      if (err) return onerror(err)
    })
  })
}
<commit_msg>0.26.0<commit_after>#!/usr/bin/env node

// maintainer note - update this manually when doing new releases:
var version = '0.26.0'

var fs = require('fs')
var os = require('os')
var path = require('path')
var extract = require('extract-zip')
var download = require('electron-download')

var platform = os.platform()

function onerror (err) {
  throw err
}

var paths = {
  darwin: path.join(__dirname, './dist/Electron.app/Contents/MacOS/Electron'),
  linux: path.join(__dirname, './dist/electron'),
  win32: path.join(__dirname, './dist/electron.exe')
}

if (!paths[platform]) throw new Error('Unknown platform: ' + platform)

// downloads if not cached
download({version: version}, extractFile)

// unzips and makes path.txt point at the correct executable
function extractFile (err, zipPath) {
  if (err) return onerror(err)
  fs.writeFile(path.join(__dirname, 'path.txt'), paths[platform], function (err) {
    if (err) return onerror(err)
    extract(zipPath, {dir: path.join(__dirname, 'dist')}, function (err) {
      if (err) return onerror(err)
    })
  })
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

var os = require('os')
var path = require('path')
var nugget = require('nugget')
var extract = require('extract-zip')
var fs = require('fs')

var platform = os.platform()
var arch = os.arch()
if ('win32' === platform) {
  // 64-bit is not available under windows.
  arch = 'ia32';
}
var version = '0.21.2'
var name = 'atom-shell-v'+version+'-'+platform+'-'+arch+'.zip'
var url = 'https://github.com/atom/atom-shell/releases/download/v'+version+'/atom-shell-v'+version+'-'+platform+'-'+arch+'.zip'

var onerror = function(err) {
  throw err
}

var paths = {
  darwin: path.join(__dirname, './dist/Atom.app/Contents/MacOS/Atom'),
  linux: path.join(__dirname, './dist/atom'),
  win32: path.join(__dirname, './dist/atom.exe')
}

var shebang = {
  darwin: '#!/bin/bash\n',
  linux: '#!/bin/bash\n',
  win32: ''
}

var argv = {
  darwin: '"$@"',
  linux: '"$@"',
  win32: '%*' // does this work with " " in the args?
}

if (!paths[platform]) throw new Error('Unknown platform: '+platform)

nugget(url, {target:name, dir:__dirname, resume:true, verbose:true}, function(err) {
  if (err) return onerror(err)
  fs.writeFileSync(path.join(__dirname, 'path.txt'), paths[platform])
  fs.writeFileSync(path.join(__dirname, 'run.bat'), shebang[platform]+'"'+paths[platform]+'" '+argv[platform])
  extract(path.join(__dirname, name), {dir:path.join(__dirname, 'dist')}, function(err) {
    if (err) return onerror(err)
  })
})
<commit_msg>0.21.3<commit_after>#!/usr/bin/env node

var os = require('os')
var path = require('path')
var nugget = require('nugget')
var extract = require('extract-zip')
var fs = require('fs')

var platform = os.platform()
var arch = os.arch()
if ('win32' === platform) {
  // 64-bit is not available under windows.
  arch = 'ia32';
}
var version = '0.21.3'
var name = 'atom-shell-v'+version+'-'+platform+'-'+arch+'.zip'
var url = 'https://github.com/atom/atom-shell/releases/download/v'+version+'/atom-shell-v'+version+'-'+platform+'-'+arch+'.zip'

var onerror = function(err) {
  throw err
}

var paths = {
  darwin: path.join(__dirname, './dist/Atom.app/Contents/MacOS/Atom'),
  linux: path.join(__dirname, './dist/atom'),
  win32: path.join(__dirname, './dist/atom.exe')
}

var shebang = {
  darwin: '#!/bin/bash\n',
  linux: '#!/bin/bash\n',
  win32: ''
}

var argv = {
  darwin: '"$@"',
  linux: '"$@"',
  win32: '%*' // does this work with " " in the args?
}

if (!paths[platform]) throw new Error('Unknown platform: '+platform)

nugget(url, {target:name, dir:__dirname, resume:true, verbose:true}, function(err) {
  if (err) return onerror(err)
  fs.writeFileSync(path.join(__dirname, 'path.txt'), paths[platform])
  fs.writeFileSync(path.join(__dirname, 'run.bat'), shebang[platform]+'"'+paths[platform]+'" '+argv[platform])
  extract(path.join(__dirname, name), {dir:path.join(__dirname, 'dist')}, function(err) {
    if (err) return onerror(err)
  })
})
<|endoftext|>


<commit_before>/**
 * @fileoverview Enforce component methods order
 * @author Yannick Croissant
 */
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/sort-comp');
var RuleTester = require('eslint').RuleTester;

var parserOptions = {
  ecmaVersion: 6,
  ecmaFeatures: {
    jsx: true
  }
};

require('babel-eslint');

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run('sort-comp', rule, {

  valid: [{
    // Must validate a full class
    code: [
      'var Hello = React.createClass({',
      '  displayName : \'\',',
      '  propTypes: {},',
      '  contextTypes: {},',
      '  childContextTypes: {},',
      '  mixins: [],',
      '  statics: {},',
      '  getDefaultProps: function() {},',
      '  getInitialState: function() {},',
      '  getChildContext: function() {},',
      '  componentWillMount: function() {},',
      '  componentDidMount: function() {},',
      '  componentWillReceiveProps: function() {},',
      '  shouldComponentUpdate: function() {},',
      '  componentWillUpdate: function() {},',
      '  componentDidUpdate: function() {},',
      '  componentWillUnmount: function() {},',
      '  render: function() {',
      '    return
Hello
;',
      '  }',
      '});'
    ].join('\n'),
    parserOptions: parserOptions
  }, {
    // Must validate a class with missing groups
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return
Hello
;',
      '  }',
      '});'
    ].join('\n'),
    parserOptions: parserOptions
  }, {
    // Must put a custom method in 'everything-else'
    code: [
      'var Hello = React.createClass({',
      '  onClick: function() {},',
      '  render: function() {',
      '    return <button onClick={this.onClick}>Hello</button>;',
      '  }',
      '});'
    ].join('\n'),
    parserOptions: parserOptions
  }, {
    // Must allow us to re-order the groups
    code: [
      'var Hello = React.createClass({',
      '  displayName : \'Hello\',',
      '  render: function() {',
      '    return <button onClick={this.onClick}>Hello</button>;',
      '  },',
      '  onClick: function() {}',
      '});'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'render',
        'everything-else'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must allow us to create a RegExp-based group
    code: [
      'class Hello extends React.Component {',
      '  customHandler() {}',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '  onClick() {}',
      '}'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'everything-else',
        'render',
        '/on.*/'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must allow us to create a named group
    code: [
      'class Hello extends React.Component {',
      '  customHandler() {}',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '  onClick() {}',
      '}'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'everything-else',
        'render',
        'customGroup'
      ],
      groups: {
        customGroup: [
          '/on.*/'
        ]
      }
    }],
    parserOptions: parserOptions
  }, {
    // Must allow a method to be in different places if it's matches multiple patterns
    code: [
      'class Hello extends React.Component {',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '  onClick() {}',
      '}'
    ].join('\n'),
    options: [{
      order: [
        '/on.*/',
        'render',
        '/.*Click/'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must allow us to use 'constructor' as a method name
    code: [
      'class Hello extends React.Component {',
      '  constructor() {}',
      '  displayName() {}',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '}'
    ].join('\n'),
    options: [{
      order: [
        'constructor',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must ignore stateless components
    code: [
      'function Hello(props) {',
      '  return
Hello {props.name}
',
      '}'
    ].join('\n'),
    parser: 'babel-eslint'
  }, {
    // Must ignore stateless components (arrow function with explicit return)
    code: [
      'var Hello = props => (',
      '
Hello {props.name}
',
      ')'
    ].join('\n'),
    parser: 'babel-eslint'
  }, {
    // Must ignore spread operator
    code: [
      'var Hello = React.createClass({',
      '  ...proto,',
      '  render: function() {',
      '    return
Hello
;',
      '  }',
      '});'
    ].join('\n'),
    parser: 'babel-eslint'
  }, {
    // Type Annotations should be first
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  constructor() {}',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    // Properties with Type Annotations should not be at the top
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  constructor() {}',
      '  state: Object = {};',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }],

  invalid: [{
    // Must force a lifecycle method to be placed before render
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return
Hello
;',
      '  },',
      '  displayName : \'Hello\',',
      '});'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after displayName'}]
  }, {
    // Must run rule when render uses createElement instead of JSX
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return React.createElement("div", null, "Hello");',
      '  },',
      '  displayName : \'Hello\',',
      '});'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after displayName'}]
  }, {
    // Must force a custom method to be placed before render
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return
Hello
;',
      '  },',
      '  onClick: function() {},',
      '});'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after onClick'}]
  }, {
    // Must force a custom method to be placed before render, even in function
    code: [
      'var Hello = () => {',
      '  return class Test extends React.Component {',
      '    render () {',
      '      return
Hello
;',
      '    }',
      '    onClick () {}',
      '  }',
      '};'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after onClick'}]
  }, {
    // Must force a custom method to be placed after render if no 'everything-else' group is specified
    code: [
      'var Hello = React.createClass({',
      '  displayName: \'Hello\',',
      '  onClick: function() {},',
      '  render: function() {',
      '    return <button onClick={this.onClick}>Hello</button>;',
      '  }',
      '});'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'render'
      ]
    }],
    parserOptions: parserOptions,
    errors: [{message: 'onClick should be placed after render'}]
  }, {
    // Must validate static properties
    code: [
      'class Hello extends React.Component {',
      '  render() {',
      '    return

',
      '  }',
      '  static displayName = \'Hello\';',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after displayName'}]
  }, {
    // Type Annotations should not be at the top by default
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  constructor() {}',
      '  state: Object = {};',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    errors: [{message: 'props should be placed after state'}]
  }, {
    // Type Annotations should be first
    code: [
      'class Hello extends React.Component {',
      '  constructor() {}',
      '  props: { text: string };',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    errors: [{message: 'constructor should be placed after props'}],
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    // Properties with Type Annotations should not be at the top
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  state: Object = {};',
      '  constructor() {}',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    errors: [{message: 'state should be placed after constructor'}],
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    code: [
      'export default class View extends React.Component {',
      '  componentDidMountOk() {}',
      '  getB() {}',
      '  componentWillMount() {}',
      '  getA() {}',
      '  render() {}',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    errors: [{message: 'componentDidMountOk should be placed after getA'}],
    options: [{
      order: [
        'static-methods',
        'lifecycle',
        '/^on.+$/',
        '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/',
        'everything-else',
        '/^render.+$/',
        'render'
      ]
    }]
  }]
});
<commit_msg>Add tests for non-react class<commit_after>/**
 * @fileoverview Enforce component methods order
 * @author Yannick Croissant
 */
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/sort-comp');
var RuleTester = require('eslint').RuleTester;

var parserOptions = {
  ecmaVersion: 6,
  ecmaFeatures: {
    jsx: true
  }
};

require('babel-eslint');

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run('sort-comp', rule, {

  valid: [{
    // Must validate a full class
    code: [
      'var Hello = React.createClass({',
      '  displayName : \'\',',
      '  propTypes: {},',
      '  contextTypes: {},',
      '  childContextTypes: {},',
      '  mixins: [],',
      '  statics: {},',
      '  getDefaultProps: function() {},',
      '  getInitialState: function() {},',
      '  getChildContext: function() {},',
      '  componentWillMount: function() {},',
      '  componentDidMount: function() {},',
      '  componentWillReceiveProps: function() {},',
      '  shouldComponentUpdate: function() {},',
      '  componentWillUpdate: function() {},',
      '  componentDidUpdate: function() {},',
      '  componentWillUnmount: function() {},',
      '  render: function() {',
      '    return
Hello
;',
      '  }',
      '});'
    ].join('\n'),
    parserOptions: parserOptions
  }, {
    // Must validate a class with missing groups
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return
Hello
;',
      '  }',
      '});'
    ].join('\n'),
    parserOptions: parserOptions
  }, {
    // Must put a custom method in 'everything-else'
    code: [
      'var Hello = React.createClass({',
      '  onClick: function() {},',
      '  render: function() {',
      '    return <button onClick={this.onClick}>Hello</button>;',
      '  }',
      '});'
    ].join('\n'),
    parserOptions: parserOptions
  }, {
    // Must allow us to re-order the groups
    code: [
      'var Hello = React.createClass({',
      '  displayName : \'Hello\',',
      '  render: function() {',
      '    return <button onClick={this.onClick}>Hello</button>;',
      '  },',
      '  onClick: function() {}',
      '});'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'render',
        'everything-else'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must allow us to create a RegExp-based group
    code: [
      'class Hello extends React.Component {',
      '  customHandler() {}',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '  onClick() {}',
      '}'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'everything-else',
        'render',
        '/on.*/'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must allow us to create a named group
    code: [
      'class Hello extends React.Component {',
      '  customHandler() {}',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '  onClick() {}',
      '}'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'everything-else',
        'render',
        'customGroup'
      ],
      groups: {
        customGroup: [
          '/on.*/'
        ]
      }
    }],
    parserOptions: parserOptions
  }, {
    // Must allow a method to be in different places if it's matches multiple patterns
    code: [
      'class Hello extends React.Component {',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '  onClick() {}',
      '}'
    ].join('\n'),
    options: [{
      order: [
        '/on.*/',
        'render',
        '/.*Click/'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must allow us to use 'constructor' as a method name
    code: [
      'class Hello extends React.Component {',
      '  constructor() {}',
      '  displayName() {}',
      '  render() {',
      '    return
Hello
;',
      '  }',
      '}'
    ].join('\n'),
    options: [{
      order: [
        'constructor',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }],
    parserOptions: parserOptions
  }, {
    // Must ignore stateless components
    code: [
      'function Hello(props) {',
      '  return
Hello {props.name}
',
      '}'
    ].join('\n'),
    parser: 'babel-eslint'
  }, {
    // Must ignore stateless components (arrow function with explicit return)
    code: [
      'var Hello = props => (',
      '
Hello {props.name}
',
      ')'
    ].join('\n'),
    parser: 'babel-eslint'
  }, {
    // Must ignore spread operator
    code: [
      'var Hello = React.createClass({',
      '  ...proto,',
      '  render: function() {',
      '    return
Hello
;',
      '  }',
      '});'
    ].join('\n'),
    parser: 'babel-eslint'
  }, {
    // Type Annotations should be first
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  constructor() {}',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    // Properties with Type Annotations should not be at the top
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  constructor() {}',
      '  state: Object = {};',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    // Non-react classes should be ignored, even in expressions
    code: [
      'return class Hello {',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '  props: { text: string };',
      '  constructor() {}',
      '  state: Object = {};',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions
  }, {
    // Non-react classes should be ignored, even in expressions
    code: [
      'return class {',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '  props: { text: string };',
      '  constructor() {}',
      '  state: Object = {};',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions
  }],

  invalid: [{
    // Must force a lifecycle method to be placed before render
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return
Hello
;',
      '  },',
      '  displayName : \'Hello\',',
      '});'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after displayName'}]
  }, {
    // Must run rule when render uses createElement instead of JSX
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return React.createElement("div", null, "Hello");',
      '  },',
      '  displayName : \'Hello\',',
      '});'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after displayName'}]
  }, {
    // Must force a custom method to be placed before render
    code: [
      'var Hello = React.createClass({',
      '  render: function() {',
      '    return
Hello
;',
      '  },',
      '  onClick: function() {},',
      '});'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after onClick'}]
  }, {
    // Must force a custom method to be placed before render, even in function
    code: [
      'var Hello = () => {',
      '  return class Test extends React.Component {',
      '    render () {',
      '      return
Hello
;',
      '    }',
      '    onClick () {}',
      '  }',
      '};'
    ].join('\n'),
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after onClick'}]
  }, {
    // Must force a custom method to be placed after render if no 'everything-else' group is specified
    code: [
      'var Hello = React.createClass({',
      '  displayName: \'Hello\',',
      '  onClick: function() {},',
      '  render: function() {',
      '    return <button onClick={this.onClick}>Hello</button>;',
      '  }',
      '});'
    ].join('\n'),
    options: [{
      order: [
        'lifecycle',
        'render'
      ]
    }],
    parserOptions: parserOptions,
    errors: [{message: 'onClick should be placed after render'}]
  }, {
    // Must validate static properties
    code: [
      'class Hello extends React.Component {',
      '  render() {',
      '    return

',
      '  }',
      '  static displayName = \'Hello\';',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    errors: [{message: 'render should be placed after displayName'}]
  }, {
    // Type Annotations should not be at the top by default
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  constructor() {}',
      '  state: Object = {};',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    parserOptions: parserOptions,
    errors: [{message: 'props should be placed after state'}]
  }, {
    // Type Annotations should be first
    code: [
      'class Hello extends React.Component {',
      '  constructor() {}',
      '  props: { text: string };',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    errors: [{message: 'constructor should be placed after props'}],
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    // Properties with Type Annotations should not be at the top
    code: [
      'class Hello extends React.Component {',
      '  props: { text: string };',
      '  state: Object = {};',
      '  constructor() {}',
      '  render() {',
      '    return
{this.props.text}
;',
      '  }',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    errors: [{message: 'state should be placed after constructor'}],
    options: [{
      order: [
        'type-annotations',
        'static-methods',
        'lifecycle',
        'everything-else',
        'render'
      ]
    }]
  }, {
    code: [
      'export default class View extends React.Component {',
      '  componentDidMountOk() {}',
      '  getB() {}',
      '  componentWillMount() {}',
      '  getA() {}',
      '  render() {}',
      '}'
    ].join('\n'),
    parser: 'babel-eslint',
    errors: [{message: 'componentDidMountOk should be placed after getA'}],
    options: [{
      order: [
        'static-methods',
        'lifecycle',
        '/^on.+$/',
        '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/',
        'everything-else',
        '/^render.+$/',
        'render'
      ]
    }]
  }]
});
<|endoftext|>


<commit_before>/**
 * The XBlockOutlineView is used to render an xblock and its children based upon the information
 * provided in the XBlockInfo model. It is a recursive set of views where each XBlock has its own instance.
 *
 * The class provides several opportunities to override the default behavior in subclasses:
 *  - shouldRenderChildren defaults to true meaning that the view should also create child views
 *  - shouldExpandChildren defaults to true meaning that the view should show itself as expanded
 *  - refresh is called when a server change has been made and the view needs to be refreshed
 *
 * The view can be constructed with an initialState option which is a JSON structure representing
 * the desired initial state. The parameters are as follows:
 *  - locator_to_show - the locator for the xblock which is the one being explicitly shown
 *  - scroll_offset - the scroll offset to use for the locator being shown
 *  - edit_display_name - true if the shown xblock's display name should be in inline edit mode
 */
define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/components/utils/view_utils',
    'js/views/utils/xblock_utils', 'js/views/xblock_string_field_editor',
    'edx-ui-toolkit/js/utils/string-utils', 'edx-ui-toolkit/js/utils/html-utils'],
    function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, XBlockStringFieldEditor, StringUtils, HtmlUtils) {
        'use strict';
        var XBlockOutlineView = BaseView.extend({
            // takes XBlockInfo as a model

            options: {
                collapsedClass: 'is-collapsed'
            },

            templateName: 'xblock-outline',

            initialize: function() {
                BaseView.prototype.initialize.call(this);
                this.initialState = this.options.initialState;
                this.expandedLocators = this.options.expandedLocators;
                this.template = this.options.template;
                if (!this.template) {
                    this.template = this.loadTemplate(this.templateName);
                }
                this.parentInfo = this.options.parentInfo;
                this.parentView = this.options.parentView;
                this.renderedChildren = false;
                this.model.on('sync', this.onSync, this);
            },

            render: function() {
                this.renderTemplate();
                this.addButtonActions(this.$el);
                this.addNameEditor();

                // For cases in which we need to suppress the header controls during rendering, we'll
                // need to add the current model's id/locator to the set of expanded locators
                if (this.model.get('is_header_visible') !== null && !this.model.get('is_header_visible')) {
                    var locator = this.model.get('id');
                    if (!_.isUndefined(this.expandedLocators) && !this.expandedLocators.contains(locator)) {
                        this.expandedLocators.add(locator);
                        this.refresh();
                    }
                }

                if (this.shouldRenderChildren() && this.shouldExpandChildren()) {
                    this.renderChildren();
                } else {
                    this.renderedChildren = false;
                }
                return this;
            },

            renderTemplate: function() {
                var html = this.template(this.getTemplateContext());
                if (this.parentInfo) {
                    this.setElement($(html));
                } else {
                    HtmlUtils.setHtml(
                        this.$el,
                        HtmlUtils.HTML(html)
                    );
                }
            },

            getTemplateContext: function() {
                var xblockInfo = this.model,
                    childInfo = xblockInfo.get('child_info'),
                    parentInfo = this.parentInfo,
                    xblockType = XBlockViewUtils.getXBlockType(this.model.get('category'), this.parentInfo),
                    xblockTypeDisplayName = XBlockViewUtils.getXBlockType(this.model.get('category'), this.parentInfo, true),
                    parentType = parentInfo ? XBlockViewUtils.getXBlockType(parentInfo.get('category')) : null,
                    addChildName = null,
                    defaultNewChildName = null,
                    isCollapsed = this.shouldRenderChildren() && !this.shouldExpandChildren();
                if (childInfo) {
                    addChildName = StringUtils.interpolate(gettext('New {component_type}'), {
                        component_type: childInfo.display_name
                    }, true);
                    defaultNewChildName = childInfo.display_name;
                }
                /* globals course */
                var userPermissions = (this.initialState && this.initialState.user_permissions) ? this.initialState.user_permissions : {};
                return {
                    userPermissions: userPermissions,
                    xblockInfo: xblockInfo,
                    visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')),
                    typeListClass: XBlockViewUtils.getXBlockListTypeClass(xblockType),
                    parentInfo: this.parentInfo,
                    xblockType: xblockType,
                    xblockTypeDisplayName: xblockTypeDisplayName,
                    parentType: parentType,
                    childType: childInfo ? XBlockViewUtils.getXBlockType(childInfo.category, xblockInfo) : null,
                    childCategory: childInfo ? childInfo.category : null,
                    addChildLabel: addChildName,
                    defaultNewChildName: defaultNewChildName,
                    isCollapsed: isCollapsed,
                    includesChildren: this.shouldRenderChildren(),
                    hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'),
                    staffOnlyMessage: this.model.get('staff_only_message'),
                    course: course
                };
            },

            renderChildren: function() {
                var self = this,
                    parentInfo = this.model;
                if (parentInfo.get('child_info')) {
                    _.each(this.model.get('child_info').children, function(childInfo) {
                        var childOutlineView = self.createChildView(childInfo, parentInfo);
                        childOutlineView.render();
                        self.addChildView(childOutlineView);
                    });
                }
                this.renderedChildren = true;
            },

            getListElement: function() {
                return this.$('> .outline-content > ol');
            },

            addChildView: function(childView, xblockElement) {
                if (xblockElement) {
                    childView.$el.insertAfter(xblockElement);
                } else {
                    this.getListElement().append(childView.$el);
                }
            },

            addNameEditor: function() {
                var self = this,
                    xblockField = this.$('.wrapper-xblock-field'),
                    XBlockOutlineFieldEditor, nameEditor;
                if (xblockField.length > 0) {
                    // Make a subclass of the standard xblock string field editor which refreshes
                    // the entire section that this view is contained in. This is necessary as
                    // changing the name could have caused the section to change state.
                    XBlockOutlineFieldEditor = XBlockStringFieldEditor.extend({
                        refresh: function() {
                            self.refresh();
                        }
                    });
                    nameEditor = new XBlockOutlineFieldEditor({
                        el: xblockField,
                        model: this.model
                    });
                    nameEditor.render();
                }
            },

            toggleExpandCollapse: function(event) {
                // The course outline page tracks expanded locators. The unit location sidebar does not.
                if (this.expandedLocators) {
                    var locator = this.model.get('id');
                    var wasExpanded = this.expandedLocators.contains(locator);
                    if (wasExpanded) {
                        this.expandedLocators.remove(locator);
                    } else {
                        this.expandedLocators.add(locator);
                    }
                }
                // Ensure that the children have been rendered before expanding
                this.ensureChildrenRendered();
                BaseView.prototype.toggleExpandCollapse.call(this, event);
            },

            /**
             * Verifies that the children are rendered (if they should be).
             */
            ensureChildrenRendered: function() {
                if (!this.renderedChildren && this.shouldRenderChildren()) {
                    this.renderChildren();
                }
            },

            /**
             * Adds handlers to the each button in the header's panel. This is managed outside of
             * Backbone's own event registration so that the handlers don't get scoped to all the
             * children of this view.
             * @param element The root element of this view.
             */
            addButtonActions: function(element) {
                var self = this;
                element.find('.delete-button').click(_.bind(this.handleDeleteEvent, this));
                element.find('.duplicate-button').click(_.bind(this.handleDuplicateEvent, this));
                element.find('.button-new').click(_.bind(this.handleAddEvent, this));
            },

            shouldRenderChildren: function() {
                return true;
            },

            shouldExpandChildren: function() {
                return true;
            },

            getChildViewClass: function() {
                return XBlockOutlineView;
            },

            createChildView: function(childInfo, parentInfo, options) {
                var viewClass = this.getChildViewClass();
                return new viewClass(_.extend({
                    model: childInfo,
                    parentInfo: parentInfo,
                    parentView: this,
                    initialState: this.initialState,
                    expandedLocators: this.expandedLocators,
                    template: this.template
                }, options));
            },

            onSync: function(event) {
                var hasChangedAttributes = ViewUtils.hasChangedAttributes(
                    this.model,
                    ['visibility_state', 'child_info', 'display_name', 'highlights']
                );
                if (hasChangedAttributes) {
                    this.onXBlockChange();
                }
            },

            onXBlockChange: function() {
                var oldElement = this.$el,
                    viewState = this.initialState;
                this.render();
                if (this.parentInfo) {
                    oldElement.replaceWith(this.$el);
                }
                if (viewState) {
                    this.setViewState(viewState);
                }
            },

            setViewState: function(viewState) {
                var locatorToShow = viewState.locator_to_show,
                    scrollOffset = viewState.scroll_offset || 0,
                    editDisplayName = viewState.edit_display_name,
                    locatorElement;
                if (locatorToShow) {
                    if (locatorToShow === this.model.id) {
                        locatorElement = this.$el;
                    } else {
                        locatorElement = this.$('.outline-item[data-locator="' + locatorToShow + '"]');
                    }
                    if (locatorElement.length > 0) {
                        ViewUtils.setScrollOffset(locatorElement, scrollOffset);
                    } else {
                        console.error('Failed to show item with locator ' + locatorToShow + '');
                    }
                    if (editDisplayName) {
                        locatorElement.find('> div[class$="header"] .xblock-field-value-edit').click();
                    }
                }
                this.initialState = null;
            },

            /**
             * Refresh the view's model from the server, which will cause the view to refresh.
             * @returns {jQuery promise} A promise representing the refresh operation.
             */
            refresh: function() {
                return this.model.fetch();
            },

            onChildAdded: function(locator, category) {
                // For units, redirect to the new page, and for everything else just refresh inline.
                if (category === 'vertical') {
                    this.onUnitAdded(locator);
                } else {
                    this.refresh();
                }
            },

            onUnitAdded: function(locator) {
                ViewUtils.redirect('/container/' + locator + '?action=new');
            },

            onChildDeleted: function() {
                this.refresh();
            },

            handleDeleteEvent: function(event) {
                var self = this,
                    parentView = this.parentView,
                    xblockType = XBlockViewUtils.getXBlockType(this.model.get('category'), parentView.model, true);
                event.preventDefault();
                XBlockViewUtils.deleteXBlock(this.model, xblockType).done(function() {
                    if (parentView) {
                        parentView.onChildDeleted(self, event);
                    }
                });
            },

            /**
             * Finds appropriate parent element for an xblock element.
             * @param {jquery Element}  xblockElement  The xblock element to be duplicated.
             * @param {String}  xblockType The front-end terminology of the xblock category.
             * @returns {jquery Element} Appropriate parent element of xblock element.
             */
            getParentElement: function(xblockElement, xblockType) {
                var xblockMap = {
                        unit: 'subsection',
                        subsection: 'section',
                        section: 'course'
                    },
                    parentXblockType = xblockMap[xblockType];
                return xblockElement.closest('.outline-' + parentXblockType);
            },

            /**
             * Duplicate event handler.
             */
            handleDuplicateEvent: function(event) {
                var self = this,
                    xblockType = XBlockViewUtils.getXBlockType(self.model.get('category'), self.parentView.model),
                    xblockElement = $(event.currentTarget).closest('.outline-item'),
                    parentElement = self.getParentElement(xblockElement, xblockType);

                event.preventDefault();
                XBlockViewUtils.duplicateXBlock(xblockElement, parentElement)
                    .done(function(data) {
                        if (self.parentView) {
                            self.parentView.onChildDuplicated(
                                data.locator,
                                xblockType,
                                xblockElement
                            );
                        }
                    });
            },

            handleAddEvent: function(event) {
                var self = this,
                    $target = $(event.currentTarget),
                    category = $target.data('category');
                event.preventDefault();
                XBlockViewUtils.addXBlock($target).done(function(locator) {
                    self.onChildAdded(locator, category, event);
                });
            }
        });

        return XBlockOutlineView;
    }); // end define();
<commit_msg>DEV-143 - Hide Studio features based on user role (bugfix)<commit_after>/**
 * The XBlockOutlineView is used to render an xblock and its children based upon the information
 * provided in the XBlockInfo model. It is a recursive set of views where each XBlock has its own instance.
 *
 * The class provides several opportunities to override the default behavior in subclasses:
 *  - shouldRenderChildren defaults to true meaning that the view should also create child views
 *  - shouldExpandChildren defaults to true meaning that the view should show itself as expanded
 *  - refresh is called when a server change has been made and the view needs to be refreshed
 *
 * The view can be constructed with an initialState option which is a JSON structure representing
 * the desired initial state. The parameters are as follows:
 *  - locator_to_show - the locator for the xblock which is the one being explicitly shown
 *  - scroll_offset - the scroll offset to use for the locator being shown
 *  - edit_display_name - true if the shown xblock's display name should be in inline edit mode
 */
define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/components/utils/view_utils',
    'js/views/utils/xblock_utils', 'js/views/xblock_string_field_editor',
    'edx-ui-toolkit/js/utils/string-utils', 'edx-ui-toolkit/js/utils/html-utils'],
    function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, XBlockStringFieldEditor, StringUtils, HtmlUtils) {
        'use strict';
        var XBlockOutlineView = BaseView.extend({
            // takes XBlockInfo as a model

            options: {
                collapsedClass: 'is-collapsed'
            },

            templateName: 'xblock-outline',

            initialize: function() {
                BaseView.prototype.initialize.call(this);
                this.initialState = this.options.initialState;
                this.expandedLocators = this.options.expandedLocators;
                this.template = this.options.template;
                if (!this.template) {
                    this.template = this.loadTemplate(this.templateName);
                }
                this.parentInfo = this.options.parentInfo;
                this.parentView = this.options.parentView;
                this.renderedChildren = false;
                this.model.on('sync', this.onSync, this);
            },

            render: function() {
                this.renderTemplate();
                this.addButtonActions(this.$el);
                this.addNameEditor();

                // For cases in which we need to suppress the header controls during rendering, we'll
                // need to add the current model's id/locator to the set of expanded locators
                if (this.model.get('is_header_visible') !== null && !this.model.get('is_header_visible')) {
                    var locator = this.model.get('id');
                    if (!_.isUndefined(this.expandedLocators) && !this.expandedLocators.contains(locator)) {
                        this.expandedLocators.add(locator);
                        this.refresh();
                    }
                }

                if (this.shouldRenderChildren() && this.shouldExpandChildren()) {
                    this.renderChildren();
                } else {
                    this.renderedChildren = false;
                }
                return this;
            },

            renderTemplate: function() {
                var html = this.template(this.getTemplateContext());
                if (this.parentInfo) {
                    this.setElement($(html));
                } else {
                    HtmlUtils.setHtml(
                        this.$el,
                        HtmlUtils.HTML(html)
                    );
                }
            },

            getTemplateContext: function() {
                var xblockInfo = this.model,
                    childInfo = xblockInfo.get('child_info'),
                    parentInfo = this.parentInfo,
                    xblockType = XBlockViewUtils.getXBlockType(this.model.get('category'), this.parentInfo),
                    xblockTypeDisplayName = XBlockViewUtils.getXBlockType(this.model.get('category'), this.parentInfo, true),
                    parentType = parentInfo ? XBlockViewUtils.getXBlockType(parentInfo.get('category')) : null,
                    addChildName = null,
                    defaultNewChildName = null,
                    isCollapsed = this.shouldRenderChildren() && !this.shouldExpandChildren();
                if (childInfo) {
                    addChildName = StringUtils.interpolate(gettext('New {component_type}'), {
                        component_type: childInfo.display_name
                    }, true);
                    defaultNewChildName = childInfo.display_name;
                }
                /* globals course */
                var userPermissions = {};
                if (this.initialState && this.initialState.user_permissions) {
                    userPermissions = this.initialState.user_permissions;
                } else if (window.courseOutlineInitialState && window.courseOutlineInitialState.user_permissions) {
                    userPermissions = window.courseOutlineInitialState.user_permissions;
                }
                return {
                    userPermissions: userPermissions,
                    xblockInfo: xblockInfo,
                    visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')),
                    typeListClass: XBlockViewUtils.getXBlockListTypeClass(xblockType),
                    parentInfo: this.parentInfo,
                    xblockType: xblockType,
                    xblockTypeDisplayName: xblockTypeDisplayName,
                    parentType: parentType,
                    childType: childInfo ? XBlockViewUtils.getXBlockType(childInfo.category, xblockInfo) : null,
                    childCategory: childInfo ? childInfo.category : null,
                    addChildLabel: addChildName,
                    defaultNewChildName: defaultNewChildName,
                    isCollapsed: isCollapsed,
                    includesChildren: this.shouldRenderChildren(),
                    hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'),
                    staffOnlyMessage: this.model.get('staff_only_message'),
                    course: course
                };
            },

            renderChildren: function() {
                var self = this,
                    parentInfo = this.model;
                if (parentInfo.get('child_info')) {
                    _.each(this.model.get('child_info').children, function(childInfo) {
                        var childOutlineView = self.createChildView(childInfo, parentInfo);
                        childOutlineView.render();
                        self.addChildView(childOutlineView);
                    });
                }
                this.renderedChildren = true;
            },

            getListElement: function() {
                return this.$('> .outline-content > ol');
            },

            addChildView: function(childView, xblockElement) {
                if (xblockElement) {
                    childView.$el.insertAfter(xblockElement);
                } else {
                    this.getListElement().append(childView.$el);
                }
            },

            addNameEditor: function() {
                var self = this,
                    xblockField = this.$('.wrapper-xblock-field'),
                    XBlockOutlineFieldEditor, nameEditor;
                if (xblockField.length > 0) {
                    // Make a subclass of the standard xblock string field editor which refreshes
                    // the entire section that this view is contained in. This is necessary as
                    // changing the name could have caused the section to change state.
                    XBlockOutlineFieldEditor = XBlockStringFieldEditor.extend({
                        refresh: function() {
                            self.refresh();
                        }
                    });
                    nameEditor = new XBlockOutlineFieldEditor({
                        el: xblockField,
                        model: this.model
                    });
                    nameEditor.render();
                }
            },

            toggleExpandCollapse: function(event) {
                // The course outline page tracks expanded locators. The unit location sidebar does not.
                if (this.expandedLocators) {
                    var locator = this.model.get('id');
                    var wasExpanded = this.expandedLocators.contains(locator);
                    if (wasExpanded) {
                        this.expandedLocators.remove(locator);
                    } else {
                        this.expandedLocators.add(locator);
                    }
                }
                // Ensure that the children have been rendered before expanding
                this.ensureChildrenRendered();
                BaseView.prototype.toggleExpandCollapse.call(this, event);
            },

            /**
             * Verifies that the children are rendered (if they should be).
             */
            ensureChildrenRendered: function() {
                if (!this.renderedChildren && this.shouldRenderChildren()) {
                    this.renderChildren();
                }
            },

            /**
             * Adds handlers to the each button in the header's panel. This is managed outside of
             * Backbone's own event registration so that the handlers don't get scoped to all the
             * children of this view.
             * @param element The root element of this view.
             */
            addButtonActions: function(element) {
                var self = this;
                element.find('.delete-button').click(_.bind(this.handleDeleteEvent, this));
                element.find('.duplicate-button').click(_.bind(this.handleDuplicateEvent, this));
                element.find('.button-new').click(_.bind(this.handleAddEvent, this));
            },

            shouldRenderChildren: function() {
                return true;
            },

            shouldExpandChildren: function() {
                return true;
            },

            getChildViewClass: function() {
                return XBlockOutlineView;
            },

            createChildView: function(childInfo, parentInfo, options) {
                var viewClass = this.getChildViewClass();
                return new viewClass(_.extend({
                    model: childInfo,
                    parentInfo: parentInfo,
                    parentView: this,
                    initialState: this.initialState,
                    expandedLocators: this.expandedLocators,
                    template: this.template
                }, options));
            },

            onSync: function(event) {
                var hasChangedAttributes = ViewUtils.hasChangedAttributes(
                    this.model,
                    ['visibility_state', 'child_info', 'display_name', 'highlights']
                );
                if (hasChangedAttributes) {
                    this.onXBlockChange();
                }
            },

            onXBlockChange: function() {
                var oldElement = this.$el,
                    viewState = this.initialState;
                this.render();
                if (this.parentInfo) {
                    oldElement.replaceWith(this.$el);
                }
                if (viewState) {
                    this.setViewState(viewState);
                }
            },

            setViewState: function(viewState) {
                var locatorToShow = viewState.locator_to_show,
                    scrollOffset = viewState.scroll_offset || 0,
                    editDisplayName = viewState.edit_display_name,
                    locatorElement;
                if (locatorToShow) {
                    if (locatorToShow === this.model.id) {
                        locatorElement = this.$el;
                    } else {
                        locatorElement = this.$('.outline-item[data-locator="' + locatorToShow + '"]');
                    }
                    if (locatorElement.length > 0) {
                        ViewUtils.setScrollOffset(locatorElement, scrollOffset);
                    } else {
                        console.error('Failed to show item with locator ' + locatorToShow + '');
                    }
                    if (editDisplayName) {
                        locatorElement.find('> div[class$="header"] .xblock-field-value-edit').click();
                    }
                }
                this.initialState = null;
            },

            /**
             * Refresh the view's model from the server, which will cause the view to refresh.
             * @returns {jQuery promise} A promise representing the refresh operation.
             */
            refresh: function() {
                return this.model.fetch();
            },

            onChildAdded: function(locator, category) {
                // For units, redirect to the new page, and for everything else just refresh inline.
                if (category === 'vertical') {
                    this.onUnitAdded(locator);
                } else {
                    this.refresh();
                }
            },

            onUnitAdded: function(locator) {
                ViewUtils.redirect('/container/' + locator + '?action=new');
            },

            onChildDeleted: function() {
                this.refresh();
            },

            handleDeleteEvent: function(event) {
                var self = this,
                    parentView = this.parentView,
                    xblockType = XBlockViewUtils.getXBlockType(this.model.get('category'), parentView.model, true);
                event.preventDefault();
                XBlockViewUtils.deleteXBlock(this.model, xblockType).done(function() {
                    if (parentView) {
                        parentView.onChildDeleted(self, event);
                    }
                });
            },

            /**
             * Finds appropriate parent element for an xblock element.
             * @param {jquery Element}  xblockElement  The xblock element to be duplicated.
             * @param {String}  xblockType The front-end terminology of the xblock category.
             * @returns {jquery Element} Appropriate parent element of xblock element.
             */
            getParentElement: function(xblockElement, xblockType) {
                var xblockMap = {
                        unit: 'subsection',
                        subsection: 'section',
                        section: 'course'
                    },
                    parentXblockType = xblockMap[xblockType];
                return xblockElement.closest('.outline-' + parentXblockType);
            },

            /**
             * Duplicate event handler.
             */
            handleDuplicateEvent: function(event) {
                var self = this,
                    xblockType = XBlockViewUtils.getXBlockType(self.model.get('category'), self.parentView.model),
                    xblockElement = $(event.currentTarget).closest('.outline-item'),
                    parentElement = self.getParentElement(xblockElement, xblockType);

                event.preventDefault();
                XBlockViewUtils.duplicateXBlock(xblockElement, parentElement)
                    .done(function(data) {
                        if (self.parentView) {
                            self.parentView.onChildDuplicated(
                                data.locator,
                                xblockType,
                                xblockElement
                            );
                        }
                    });
            },

            handleAddEvent: function(event) {
                var self = this,
                    $target = $(event.currentTarget),
                    category = $target.data('category');
                event.preventDefault();
                XBlockViewUtils.addXBlock($target).done(function(locator) {
                    self.onChildAdded(locator, category, event);
                });
            }
        });

        return XBlockOutlineView;
    }); // end define();
<|endoftext|>


<commit_before>;(function () {
	var m = (typeof global !== 'undefined')
		? (global.m || require('mithril'))
		: window.m

	if (!m) throw ("mithril-datepicker can't find Mithril.js")

	var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
	var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
	var prevNextTitles = ['1 Mo', '1 Yr', '10 Yr']
	var weekStart = 0
	var locale = 'en-us'
	var formatOptions = null

	/***************************************
	 *
	 * actions
	 *
	 ***************************************/

	function chooseDate(props, e) {
		var box = e.target
		var selectedDate = parseInt(box.textContent)
		var dateObj = props.date
		if (box.classList.contains('other-scope')) {
			dateObj.setFullYear(dateObj.getFullYear(), dateObj.getMonth() + (selectedDate > 6 ? -1 : 1), selectedDate)
		} else {
			dateObj.setDate(selectedDate)
		}
	}

	function dismissAndCommit(props, onchange) {
		props.view = 0
		props.active = false
		if (onchange) onchange(props.date)
	}

	function prevNext(props, delta){
		var newDate = new Date(props.date)
		switch (props.view) {
			case 0:
				newDate.setMonth(newDate.getMonth() + delta)
				break
			case 1:
				newDate.setFullYear(newDate.getFullYear() + delta)
				break
			default:
				newDate.setFullYear(newDate.getFullYear() + (delta * 10))
		}
		props.date = pushToLastDay(props.date, newDate)
	}

	/***************************************
	 *
	 * utility
	 *
	 ***************************************/

	function adjustedProps(date, delta) {
		var month = date.getMonth() + delta, year = date.getFullYear()
		var over = month > 11, under = month < 0
		return {
			month: over ? 0 : under ? 11 : month,
			year: over ? year + 1 : under ? year - 1 : year
		}
	}

	function lastDateInMonth(date, delta) {
		var obj = adjustedProps(date, delta)
		if ([0, 2, 4, 6, 7, 9, 11].indexOf(obj.month) > -1) return 31 // array of 31-day props.months
		if (obj.month === 1) { // February
			if (!(obj.year % 400)) return 29
			if (!(obj.year % 100)) return 28
			return (obj.year % 4) ? 28 : 29
		}
		return 30
	}

	function pushToLastDay(oldDate, newDate) {
		if (oldDate.getDate() !== newDate.getDate()) {
			newDate.setMonth(newDate.getMonth() - 1, lastDateInMonth(newDate, -1))
		}
		return newDate
	}

	function stringsForLocale(locale) {
		var date = new Date('jan 1 2017'), _months = [], _days = [] // 1/1/2017 was month:0 and weekday:0, so perfect
		while (_days.length < 7) {
			_days.push(date.toLocaleDateString(locale, { weekday: 'long' }))
			date.setDate(date.getDate() + 1)
		}
		while (_months.length < 12) {
			_months.push(date.toLocaleDateString(locale, { month: 'long' }))
			date.setMonth(date.getMonth() + 1)
		}
		return { days: _days, months: _months }
	}

	function wrapAround(idx, array) {
		var len = array.length
		var n = idx >= len ? idx - len : idx
		return array[n]
	}

	/***************************************
	 *
	 * generators
	 *
	 ***************************************/

	function daysFromLastMonth(props){
		var month = props.date.getMonth(), year = props.date.getFullYear()
		var firstDay = (new Date(year, month, 1)).getDay() - props.weekStart
		if (firstDay < 0) firstDay += 7
		var array = []
		var lastDate = lastDateInMonth(props.date, -1)
		var offsetStart = lastDate - firstDay + 1
		for (var i=offsetStart; i<=lastDate; i++) { array.push(i) }
		return array
	}

	function daysFromThisMonth(props) {
		var max = lastDateInMonth(props.date, 0)
		var array = []
		for (var i=1; i<=max; i++) {
			array.push(i)
		}
		return array
	}

	function daysFromNextMonth(prev, these) {
		var soFar = prev.concat(these)
		var mod = soFar.length % 7
		var array = []
		if (mod > 0) {
			var n = 7 - mod
			for (var i=1; i<=n; i++) { array.push(i) }
		}
		return array
	}

	function defaultDate() {
		var now = new Date()
		now.setHours(0, 0, 0, 0)
		return now
	}

	function yearsForDecade(date) {
		var year = date.getFullYear()
		var start = year - (year % 10)
		var array = []
		for (var i=start; i<start+10; i++) { array.push(i) }
		return array
	}

	/***************************************
	 *
	 * view helpers
	 *
	 ***************************************/

	function classForBox(a, b) { return a === b ? 'chosen' : '' }

	function displayDate(props) {
		return props.date
			.toLocaleDateString(props.locale, props.formatOptions || {
				weekday: 'short',
				month: 'short',
				day: 'numeric',
				year: 'numeric'
			})
	}

	/***************************************
	 *
	 * components
	 *
	 ***************************************/

	var Header = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var date = props.date
			var theseMonths = props.months || months
			return m('.header'
				, m('.button-bg', { class: 'v' + props.view })
				, m('.fake-border')
				, m('button.prev'
					, { onclick: prevNext.bind(null, props, -1) }
					, prevNextTitles[props.view]
				)
				, m('button.segment', { onclick: function () { props.view = 0 } }, date.getDate())
				, m('button.segment', { onclick: function () { props.view = 1 } }, theseMonths[date.getMonth()].substr(0, 3))
				, m('button.segment', { onclick: function () { props.view = 2 } }, date.getFullYear())
				, m('button.next'
					, { onclick: prevNext.bind(null, props, 1) }
					, prevNextTitles[props.view]
				)
			)
		}
	}

	var MonthView = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var prevDates = daysFromLastMonth(props)
			var theseDates = daysFromThisMonth(props)
			var nextDates = daysFromNextMonth(prevDates, theseDates)
			var theseWeekdays = props.days || days
			return m('.calendar'
				, m('.weekdays'
					, theseWeekdays.map(function (_, idx) {
						var day = wrapAround(idx + props.weekStart, theseWeekdays)
						return m('.day.dummy', day.substring(0, 2))
					})
				)
				, m('.weekdays'
					, {
						onclick: function(e){
							chooseDate(props, e)
							dismissAndCommit(props, vnode.attrs.onchange)
						}
					}
					, prevDates.map(function (date) {
						return m('button.day.other-scope', date)
					})
					, theseDates.map(function (date) {
						return m('button.day'
							, { class: classForBox(props.date.getDate(), date) }
							, m('.number', date)
						)
					})
					, nextDates.map(function (date) {
						return m('button.day.other-scope', date)
					})
				)
			)

		}
	}

	var YearView = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var theseMonths = props.months || months
			return m('.calendar'
				, m('.months'
					, theseMonths.map(function (month, idx) {
						return m('button.month'
							, {
								class: classForBox(props.date.getMonth(), idx),
								onclick: function () {
									var newDate = new Date(props.date)
									newDate.setMonth(idx)
									props.date = pushToLastDay(props.date, newDate)
									props.view = 0
								}
							}
							, m('.number', month.substring(0, 3))
						)
					})
				)
			)
		}
	}

	var DecadeView = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var decade = yearsForDecade(props.date)
			return m('.calendar'
				, m('.years'
					, decade.map(function (year) {
						return m('button.year'
							, {
								class: classForBox(props.date.getFullYear(), year),
								onclick: function () {
									var newDate = new Date(props.date)
									newDate.setFullYear(year)
									props.date = pushToLastDay(props.date, newDate)
									props.view = 1
								}
							}
							, m('.number', year)
						)
					})
				)
			)
		}
	}

	var Editor = {
		oncreate: function (vnode) {
			requestAnimationFrame(function () { vnode.dom.classList.add('active') })
		},
		onbeforeremove: function (vnode) {
			vnode.dom.classList.remove('active')
			return new Promise(function (done) { setTimeout(done, 200) })
		},
		view: function (vnode) {
			var props = vnode.attrs.props
			return m('.editor'
				, m(Header, { props: props })
				, m('.sled'
					, { class: 'p' + props.view }
					, m(MonthView, { props: props, onchange: vnode.attrs.onchange })
					, m(YearView, { props: props })
					, m(DecadeView, {props: props })
				)
			)
		}
	}

	var DatePicker = {
		localize: function (loc) {
			if (loc) {
				prevNextTitles = loc.prevNextTitles || prevNextTitles
				locale = loc.locale || locale
				formatOptions = loc.formatOptions || formatOptions
				weekStart = typeof loc.weekStart === 'number'
					? loc.weekStart
					: weekStart

				var strings = stringsForLocale(locale)
				days = strings.days
				months = strings.months
			}
		},
		oninit: function (vnode) {
			var attrs = vnode.attrs
			var props = {
				date: new Date(attrs.date || defaultDate()),
				active: false,
				view: 0
			}

			;['prevNextTitles', 'locale', 'formatOptions'].forEach(function (prop) {
				props[prop] = attrs[prop] || eval(prop)
			})
			props.weekStart = typeof attrs.weekStart === 'number' ? attrs.weekStart : weekStart

			if (attrs.locale && attrs.locale !== locale) {
				var strings = stringsForLocale(props.locale)
				props.days = strings.days
				props.months = strings.months
			}

			vnode.state.props = props
		},
		view: function(vnode){
			var props = vnode.state.props
			var displayText = displayDate(props)
			return m('.mithril-date-picker-container'
				, { class: props.active ? 'active' : '' }
				, m('.mithril-date-picker'
					, m('.button.current-date'
						, {
							onclick: function(){
								if (props.active) props.view = 0
								props.active = !props.active
							}
						}
						, displayText
					)
					, props.active && m('.overlay', { onclick: dismissAndCommit.bind(null, props, vnode.attrs.onchange) })
					, props.active && m(Editor, { props: props, onchange: vnode.attrs.onchange })
				)
			)
		}
	}

	if (typeof module === 'object') module.exports = DatePicker
	else if (typeof window !== 'undefined') window.DatePicker = DatePicker
	else global.DatePicker = DatePicker
})()<commit_msg>Remove an unnecessary `eval` call<commit_after>;(function () {
	var m = (typeof global !== 'undefined')
		? (global.m || require('mithril'))
		: window.m

	if (!m) throw ("mithril-datepicker can't find Mithril.js")

	var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
	var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
	var prevNextTitles = ['1 Mo', '1 Yr', '10 Yr']
	var weekStart = 0
	var locale = 'en-us'
	var formatOptions = null

	/***************************************
	 *
	 * actions
	 *
	 ***************************************/

	function chooseDate(props, e) {
		var box = e.target
		var selectedDate = parseInt(box.textContent)
		var dateObj = props.date
		if (box.classList.contains('other-scope')) {
			dateObj.setFullYear(dateObj.getFullYear(), dateObj.getMonth() + (selectedDate > 6 ? -1 : 1), selectedDate)
		} else {
			dateObj.setDate(selectedDate)
		}
	}

	function dismissAndCommit(props, onchange) {
		props.view = 0
		props.active = false
		if (onchange) onchange(props.date)
	}

	function prevNext(props, delta){
		var newDate = new Date(props.date)
		switch (props.view) {
			case 0:
				newDate.setMonth(newDate.getMonth() + delta)
				break
			case 1:
				newDate.setFullYear(newDate.getFullYear() + delta)
				break
			default:
				newDate.setFullYear(newDate.getFullYear() + (delta * 10))
		}
		props.date = pushToLastDay(props.date, newDate)
	}

	/***************************************
	 *
	 * utility
	 *
	 ***************************************/

	function adjustedProps(date, delta) {
		var month = date.getMonth() + delta, year = date.getFullYear()
		var over = month > 11, under = month < 0
		return {
			month: over ? 0 : under ? 11 : month,
			year: over ? year + 1 : under ? year - 1 : year
		}
	}

	function lastDateInMonth(date, delta) {
		var obj = adjustedProps(date, delta)
		if ([0, 2, 4, 6, 7, 9, 11].indexOf(obj.month) > -1) return 31 // array of 31-day props.months
		if (obj.month === 1) { // February
			if (!(obj.year % 400)) return 29
			if (!(obj.year % 100)) return 28
			return (obj.year % 4) ? 28 : 29
		}
		return 30
	}

	function pushToLastDay(oldDate, newDate) {
		if (oldDate.getDate() !== newDate.getDate()) {
			newDate.setMonth(newDate.getMonth() - 1, lastDateInMonth(newDate, -1))
		}
		return newDate
	}

	function stringsForLocale(locale) {
		var date = new Date('jan 1 2017'), _months = [], _days = [] // 1/1/2017 was month:0 and weekday:0, so perfect
		while (_days.length < 7) {
			_days.push(date.toLocaleDateString(locale, { weekday: 'long' }))
			date.setDate(date.getDate() + 1)
		}
		while (_months.length < 12) {
			_months.push(date.toLocaleDateString(locale, { month: 'long' }))
			date.setMonth(date.getMonth() + 1)
		}
		return { days: _days, months: _months }
	}

	function wrapAround(idx, array) {
		var len = array.length
		var n = idx >= len ? idx - len : idx
		return array[n]
	}

	/***************************************
	 *
	 * generators
	 *
	 ***************************************/

	function daysFromLastMonth(props){
		var month = props.date.getMonth(), year = props.date.getFullYear()
		var firstDay = (new Date(year, month, 1)).getDay() - props.weekStart
		if (firstDay < 0) firstDay += 7
		var array = []
		var lastDate = lastDateInMonth(props.date, -1)
		var offsetStart = lastDate - firstDay + 1
		for (var i=offsetStart; i<=lastDate; i++) { array.push(i) }
		return array
	}

	function daysFromThisMonth(props) {
		var max = lastDateInMonth(props.date, 0)
		var array = []
		for (var i=1; i<=max; i++) {
			array.push(i)
		}
		return array
	}

	function daysFromNextMonth(prev, these) {
		var soFar = prev.concat(these)
		var mod = soFar.length % 7
		var array = []
		if (mod > 0) {
			var n = 7 - mod
			for (var i=1; i<=n; i++) { array.push(i) }
		}
		return array
	}

	function defaultDate() {
		var now = new Date()
		now.setHours(0, 0, 0, 0)
		return now
	}

	function yearsForDecade(date) {
		var year = date.getFullYear()
		var start = year - (year % 10)
		var array = []
		for (var i=start; i<start+10; i++) { array.push(i) }
		return array
	}

	/***************************************
	 *
	 * view helpers
	 *
	 ***************************************/

	function classForBox(a, b) { return a === b ? 'chosen' : '' }

	function displayDate(props) {
		return props.date
			.toLocaleDateString(props.locale, props.formatOptions || {
				weekday: 'short',
				month: 'short',
				day: 'numeric',
				year: 'numeric'
			})
	}

	/***************************************
	 *
	 * components
	 *
	 ***************************************/

	var Header = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var date = props.date
			var theseMonths = props.months || months
			return m('.header'
				, m('.button-bg', { class: 'v' + props.view })
				, m('.fake-border')
				, m('button.prev'
					, { onclick: prevNext.bind(null, props, -1) }
					, prevNextTitles[props.view]
				)
				, m('button.segment', { onclick: function () { props.view = 0 } }, date.getDate())
				, m('button.segment', { onclick: function () { props.view = 1 } }, theseMonths[date.getMonth()].substr(0, 3))
				, m('button.segment', { onclick: function () { props.view = 2 } }, date.getFullYear())
				, m('button.next'
					, { onclick: prevNext.bind(null, props, 1) }
					, prevNextTitles[props.view]
				)
			)
		}
	}

	var MonthView = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var prevDates = daysFromLastMonth(props)
			var theseDates = daysFromThisMonth(props)
			var nextDates = daysFromNextMonth(prevDates, theseDates)
			var theseWeekdays = props.days || days
			return m('.calendar'
				, m('.weekdays'
					, theseWeekdays.map(function (_, idx) {
						var day = wrapAround(idx + props.weekStart, theseWeekdays)
						return m('.day.dummy', day.substring(0, 2))
					})
				)
				, m('.weekdays'
					, {
						onclick: function(e){
							chooseDate(props, e)
							dismissAndCommit(props, vnode.attrs.onchange)
						}
					}
					, prevDates.map(function (date) {
						return m('button.day.other-scope', date)
					})
					, theseDates.map(function (date) {
						return m('button.day'
							, { class: classForBox(props.date.getDate(), date) }
							, m('.number', date)
						)
					})
					, nextDates.map(function (date) {
						return m('button.day.other-scope', date)
					})
				)
			)

		}
	}

	var YearView = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var theseMonths = props.months || months
			return m('.calendar'
				, m('.months'
					, theseMonths.map(function (month, idx) {
						return m('button.month'
							, {
								class: classForBox(props.date.getMonth(), idx),
								onclick: function () {
									var newDate = new Date(props.date)
									newDate.setMonth(idx)
									props.date = pushToLastDay(props.date, newDate)
									props.view = 0
								}
							}
							, m('.number', month.substring(0, 3))
						)
					})
				)
			)
		}
	}

	var DecadeView = {
		view: function (vnode) {
			var props = vnode.attrs.props
			var decade = yearsForDecade(props.date)
			return m('.calendar'
				, m('.years'
					, decade.map(function (year) {
						return m('button.year'
							, {
								class: classForBox(props.date.getFullYear(), year),
								onclick: function () {
									var newDate = new Date(props.date)
									newDate.setFullYear(year)
									props.date = pushToLastDay(props.date, newDate)
									props.view = 1
								}
							}
							, m('.number', year)
						)
					})
				)
			)
		}
	}

	var Editor = {
		oncreate: function (vnode) {
			requestAnimationFrame(function () { vnode.dom.classList.add('active') })
		},
		onbeforeremove: function (vnode) {
			vnode.dom.classList.remove('active')
			return new Promise(function (done) { setTimeout(done, 200) })
		},
		view: function (vnode) {
			var props = vnode.attrs.props
			return m('.editor'
				, m(Header, { props: props })
				, m('.sled'
					, { class: 'p' + props.view }
					, m(MonthView, { props: props, onchange: vnode.attrs.onchange })
					, m(YearView, { props: props })
					, m(DecadeView, {props: props })
				)
			)
		}
	}

	var DatePicker = {
		localize: function (loc) {
			if (loc) {
				prevNextTitles = loc.prevNextTitles || prevNextTitles
				locale = loc.locale || locale
				formatOptions = loc.formatOptions || formatOptions
				weekStart = typeof loc.weekStart === 'number'
					? loc.weekStart
					: weekStart

				var strings = stringsForLocale(locale)
				days = strings.days
				months = strings.months
			}
		},
		oninit: function (vnode) {
			var attrs = vnode.attrs
			var props = {
				date: new Date(attrs.date || defaultDate()),
				active: false,
				view: 0
			}

			props.prevNextTitles = attrs.prevNextTitles || prevNextTitles
			props.locale = attrs.locale || locale
			props.formatOptions = attrs.formatOptions || formatOptions
			props.weekStart = typeof attrs.weekStart === 'number' ? attrs.weekStart : weekStart

			if (attrs.locale && attrs.locale !== locale) {
				var strings = stringsForLocale(props.locale)
				props.days = strings.days
				props.months = strings.months
			}

			vnode.state.props = props
		},
		view: function(vnode){
			var props = vnode.state.props
			var displayText = displayDate(props)
			return m('.mithril-date-picker-container'
				, { class: props.active ? 'active' : '' }
				, m('.mithril-date-picker'
					, m('.button.current-date'
						, {
							onclick: function(){
								if (props.active) props.view = 0
								props.active = !props.active
							}
						}
						, displayText
					)
					, props.active && m('.overlay', { onclick: dismissAndCommit.bind(null, props, vnode.attrs.onchange) })
					, props.active && m(Editor, { props: props, onchange: vnode.attrs.onchange })
				)
			)
		}
	}

	if (typeof module === 'object') module.exports = DatePicker
	else if (typeof window !== 'undefined') window.DatePicker = DatePicker
	else global.DatePicker = DatePicker
})()
<|endoftext|>


<commit_before>define([
	"dojo/_base/declare",
	"./Pane",
	"./_ContentPaneMixin"
], function(declare, Pane, ContentPaneMixin){

	// module:
	//		dojox/mobile/ContentPane
	// summary:
	//		A very simple content pane to embed an HTML fragment.

	return declare("dojox.mobile.ContentPane", [Pane, ContentPaneMixin], {
		// summary:
		//		A very simple content pane to embed an HTML fragment.
		// description:
		//		This widget embeds an HTML fragment and run the parser. It has
		//		ability to load external content using dojo/_base/xhr. onLoad()
		//		is called when parsing is done and the content is
		//		ready. Compared with dijit.layout.ContentPane, this widget
		//		provides only basic fuctionality, but it is much smaller than
		//		dijit.layout.ContentPane.

		baseClass: "mblContentPane"
	});
});
<commit_msg>Refs #14638 dojox.mobile.ContentPane should also inherit from dijit._Container. !strict<commit_after>define([
	"dojo/_base/declare",
	"./Container",
	"./_ContentPaneMixin"
], function(declare, Container, ContentPaneMixin){

	// module:
	//		dojox/mobile/ContentPane
	// summary:
	//		A very simple content pane to embed an HTML fragment.

	return declare("dojox.mobile.ContentPane", [Container, ContentPaneMixin], {
		// summary:
		//		A very simple content pane to embed an HTML fragment.
		// description:
		//		This widget embeds an HTML fragment and run the parser. It has
		//		ability to load external content using dojo/_base/xhr. onLoad()
		//		is called when parsing is done and the content is
		//		ready. Compared with dijit.layout.ContentPane, this widget
		//		provides only basic fuctionality, but it is much smaller than
		//		dijit.layout.ContentPane.

		baseClass: "mblContentPane"
	});
});
<|endoftext|>