<commit_before>import React from 'react'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'
import { goToTop, goToAnchor } from 'react-scrollable-anchor'

import { Divider, Grid, Header } from 'semantic-ui-react'

import * as accountActions from '../actions/accountActions'
import * as postActions from '../actions/postActions'
import * as preferenceActions from '../actions/preferenceActions'
import * as statusActions from '../actions/statusActions'

import Post from '../components/elements/post'
import Response from '../components/elements/response'
import Paginator from '../components/global/paginator'

class Thread extends React.Component {

  componentWillMount() {
    this.fetchPost()
  }

  fetchPost() {
    this.props.actions.resetPostState()
    this.props.actions.fetchPost(this.props.match.params)
    this.props.actions.fetchPostResponses(this.props.match.params)
    if (!this.props.account) {
      this.props.actions.fetchAccount()
    }
  }

  scrollToPost = (id) => {
    let page = this.getPageForPost(id)
    // Couldn't determine page, goto last and bottom
    if(page === false) {
      let total = this.props.post.responses.length,
          perPage = this.props.preferences.threadPostsPerPage
      page = Math.ceil(total / perPage)
    }
    // Change page and focus
    this.changePage(page, id)
  }

  getPageForPost = (id) => {
    let collection = this.props.post.responses,
        perPage = this.props.preferences.threadPostsPerPage,
        position = false
    for(var i = 0; i < collection.length; i++) {
      if(collection[i]['_id'] === id) {
        position = i
      }
    }
    if(position === false) return position
    return Math.floor(position / perPage) + 1;
  }

  changePage = (page, scrollTo = false) => {
    let state = {
      page: page
    }
    if(scrollTo) {
      state.scrollTo = scrollTo
    } else {
      goToTop()
    }
    this.setState(state)
  }

  componentDidUpdate() {
    const anchor = (this.state) ? this.state.scrollTo : false
    if(this.state && this.state.scrollTo && this.props.post.responses.length > 0) {
      this.setState({scrollTo: false})
      setTimeout(function() {
        goToAnchor(anchor)
      }, 50)
    }
    if(this.state && this.state.scrollToWhenReady && this.props.post.responses.length > 0) {
      this.scrollToPost(this.state.scrollToWhenReady)
      this.setState({
        scrollToWhenReady: false
      })
    }
    if(this.props.match.url !== this.props.post.content.url) {
      this.fetchPost();
    }
  }

  componentDidMount() {
    const { hash } = location;
    const regexPage = /#comments-page-(\d+)+$/
    const regexPost = /#@?([A-Za-z0-9\-_]+)\/([A-Za-z0-9\-_]+)$/
    let matchesPage = hash.match(regexPage)
    let matchesPost = hash.match(regexPost)
    if(matchesPage) {
      this.setState({
        page: parseInt(matchesPage[1], 10)
      })
    }
    if(matchesPost) {
      let anchor = matchesPost[1] + '/' + matchesPost[2]
      this.setState({
        scrollToWhenReady: anchor
      })
    }
  }

  render() {
    let page = (this.state) ? this.state.page : 1,
        perPage = this.props.preferences.threadPostsPerPage,
        responses = (this.props.post) ? this.props.post.responses : 0,
        pages = Math.ceil(responses.length / perPage)
    let comments_nav = (
      <Grid id={(page ? `comments-page-${page}` : '')}>
        <Grid.Row verticalAlign='middle'>
          <Grid.Column only='tablet computer' width={4}>

              Comments ({responses.length})

                Page {page} of {pages}

          </Grid.Column>
          <Grid.Column mobile={16} tablet={12} computer={12}>
            <Paginator
              page={page}
              perPage={perPage}
              total={responses.length}
              callback={this.changePage}
              />
          </Grid.Column>
        </Grid.Row>
      </Grid>
    )
    return (

        <Post
          page={page}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props }/>
        <Divider></Divider>
        { comments_nav }
        <Divider horizontal>Page {page}</Divider>
        <Response
          page={page}
          perPage={perPage}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props } />
        <Divider horizontal id='comments-new'>Page {page}</Divider>
        { comments_nav }

    )
  }
}

function mapStateToProps(state, ownProps) {
  return {
    post: state.post,
    preferences: state.preferences,
    status: state.status
  }
}

function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators({
    ...accountActions,
    ...postActions,
    ...preferenceActions,
    ...statusActions
  }, dispatch)}
}

export default connect(mapStateToProps, mapDispatchToProps)(Thread);
<commit_msg>Removed this hotfix, was causing multiple loads<commit_after>import React from 'react'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'
import { goToTop, goToAnchor } from 'react-scrollable-anchor'

import { Divider, Grid, Header } from 'semantic-ui-react'

import * as accountActions from '../actions/accountActions'
import * as postActions from '../actions/postActions'
import * as preferenceActions from '../actions/preferenceActions'
import * as statusActions from '../actions/statusActions'

import Post from '../components/elements/post'
import Response from '../components/elements/response'
import Paginator from '../components/global/paginator'

class Thread extends React.Component {

  componentWillMount() {
    this.fetchPost()
  }

  fetchPost() {
    this.props.actions.resetPostState()
    this.props.actions.fetchPost(this.props.match.params)
    this.props.actions.fetchPostResponses(this.props.match.params)
    if (!this.props.account) {
      this.props.actions.fetchAccount()
    }
  }

  scrollToPost = (id) => {
    let page = this.getPageForPost(id)
    // Couldn't determine page, goto last and bottom
    if(page === false) {
      let total = this.props.post.responses.length,
          perPage = this.props.preferences.threadPostsPerPage
      page = Math.ceil(total / perPage)
    }
    // Change page and focus
    this.changePage(page, id)
  }

  getPageForPost = (id) => {
    let collection = this.props.post.responses,
        perPage = this.props.preferences.threadPostsPerPage,
        position = false
    for(var i = 0; i < collection.length; i++) {
      if(collection[i]['_id'] === id) {
        position = i
      }
    }
    if(position === false) return position
    return Math.floor(position / perPage) + 1;
  }

  changePage = (page, scrollTo = false) => {
    let state = {
      page: page
    }
    if(scrollTo) {
      state.scrollTo = scrollTo
    } else {
      goToTop()
    }
    this.setState(state)
  }

  componentDidUpdate() {
    const anchor = (this.state) ? this.state.scrollTo : false
    if(this.state && this.state.scrollTo && this.props.post.responses.length > 0) {
      this.setState({scrollTo: false})
      setTimeout(function() {
        goToAnchor(anchor)
      }, 50)
    }
    if(this.state && this.state.scrollToWhenReady && this.props.post.responses.length > 0) {
      this.scrollToPost(this.state.scrollToWhenReady)
      this.setState({
        scrollToWhenReady: false
      })
    }
  }

  componentDidMount() {
    const { hash } = location;
    const regexPage = /#comments-page-(\d+)+$/
    const regexPost = /#@?([A-Za-z0-9\-_]+)\/([A-Za-z0-9\-_]+)$/
    let matchesPage = hash.match(regexPage)
    let matchesPost = hash.match(regexPost)
    if(matchesPage) {
      this.setState({
        page: parseInt(matchesPage[1], 10)
      })
    }
    if(matchesPost) {
      let anchor = matchesPost[1] + '/' + matchesPost[2]
      this.setState({
        scrollToWhenReady: anchor
      })
    }
  }

  render() {
    let page = (this.state) ? this.state.page : 1,
        perPage = this.props.preferences.threadPostsPerPage,
        responses = (this.props.post) ? this.props.post.responses : 0,
        pages = Math.ceil(responses.length / perPage)
    let comments_nav = (
      <Grid id={(page ? `comments-page-${page}` : '')}>
        <Grid.Row verticalAlign='middle'>
          <Grid.Column only='tablet computer' width={4}>

              Comments ({responses.length})

                Page {page} of {pages}

          </Grid.Column>
          <Grid.Column mobile={16} tablet={12} computer={12}>
            <Paginator
              page={page}
              perPage={perPage}
              total={responses.length}
              callback={this.changePage}
              />
          </Grid.Column>
        </Grid.Row>
      </Grid>
    )
    return (

        <Post
          page={page}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props }/>
        <Divider></Divider>
        { comments_nav }
        <Divider horizontal>Page {page}</Divider>
        <Response
          page={page}
          perPage={perPage}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props } />
        <Divider horizontal id='comments-new'>Page {page}</Divider>
        { comments_nav }

    )
  }
}

function mapStateToProps(state, ownProps) {
  return {
    post: state.post,
    preferences: state.preferences,
    status: state.status
  }
}

function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators({
    ...accountActions,
    ...postActions,
    ...preferenceActions,
    ...statusActions
  }, dispatch)}
}

export default connect(mapStateToProps, mapDispatchToProps)(Thread);
<|endoftext|>


<commit_before>// Author: Rok Garbas
// Contact: rok@garbas.si
// Version: 1.0
// Description:
//
// License:
//
// Copyright (C) 2010 Plone Foundation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

/*jshint bitwise:true, curly:true, eqeqeq:true, immed:true, latedef:true,
  newcap:true, noarg:true, noempty:true, nonew:true, plusplus:true,
  undef:true, strict:true, trailing:true, browser:true, evil:true */
/*global define:false, window:false */

if (window.jQuery) {
  define( "jquery", [], function () {
    "use strict";
    return window.jQuery;
  } );
}

define([
  'jquery',
  'jam/Patterns/src/registry',
  'js/patterns/select2',
  'js/patterns/pickadate',
  'js/patterns/autotoc',
  'js/patterns/accessibility',
  'js/patterns/relateditems',
  'js/patterns/formUnloadAlert',
  'js/patterns/toggle',
  'js/patterns/tinymce',
  'js/patterns/picture',
  'js/patterns/livesearch',
  'js/patterns/querystring'
], function($, registry) {
  "use strict";

  // BBB: we need to hook pattern to classes which plone was using until now
  var Widgets = {
    name: "plone-widgets",
    transform: function($root) {

      // apply autotoc pattern where enableFormTabbing exists
      var $match = $root.filter('.enableFormTabbing');
      $match = $match.add($root.find('.enableFormTabbing'));
      $match.addClass('pat-autotoc');
      $match.attr({
        'data-pat-autotoc':'levels: legend;section: fieldset;klass: autotabs'
      });

      // activate accessibility pattern by default
      $root.addClass('pat-accessibility');
      $root.attr({
        'data-pat-accessibility': 'smallbtn: #accessibility-smallText;normalbtn: #accessibility-normalText;largebtn: #accessibility-largeText'
      });

      // apply formUnloadAlert pattern where enableUnloadProtection exists
      var $match = $root.filter('.enableUnloadProtection');
      $match = $match.add($root.find('.enableUnloadProtection'));
      $match.addClass('pat-formunloadalert');
      $match.attr({
        'data-pat-formunloadalert':'message: '+window.form_modified_message
      });

      // Fix drop downs
      var personal_tools = $('dl.actionMenu#portal-personaltools dt.actionMenuHeader a');
      personal_tools.addClass('pat-toggle');
      personal_tools.attr({
        'data-pat-toggle': 'target: dl.actionMenu#portal-personaltools;value: activated'
      });

      var add_new = $('dl.actionMenu#plone-contentmenu-factories dt.actionMenuHeader a');
      add_new.addClass('pat-toggle');
      add_new.attr({
        'data-pat-toggle': 'target: dl.actionMenu#plone-contentmenu-factories;value: activated'
      });

      var display = $('dl.actionMenu#plone-contentmenu-display dt.actionMenuHeader a');
      display.addClass('pat-toggle');
      display.attr({
        'data-pat-toggle': 'target: dl.actionMenu#plone-contentmenu-display;value: activated'
      });

      var workflow = $('dl.actionMenu#plone-contentmenu-workflow dt.actionMenuHeader a');
      workflow.addClass('pat-toggle');
      workflow.attr({
        'data-pat-toggle': 'target: dl.actionMenu#plone-contentmenu-workflow;value: activated'
      });

      $('dl.actionMenu').removeClass('deactivated');

      $('html').on('mousedown', function(e) {
        if (!$(e.toElement).parents('dl.actionMenu').hasClass('activated')){
          // Means we clicked an opened menu, do not close
          $('dl.actionMenu').removeClass('activated');
        }
      });

      // Live Search
      $match = $root.find('.LSBox');
      var url = $match.parents('form').attr('action').replace('@@search', '@@updated_search');
      var attrs = {
        'url': url,
        'results': {
          'content': '#search-results',
          'item': 'dt'
        }
      };
      $match.attr({
        'class': 'pat-livesearch',
        'data-pat-livesearch': JSON.stringify(attrs)
      });
      $match.find('.searchSection').remove();
      $match.find('.LSResult').attr({
        'class': 'pat-livesearch-container pull-right',
        'id': ''
      });
      $match.find('.LSShadow').attr('class', 'pat-livesearch-results');
      $match.find('#searchGadget').addClass('pat-livesearch-input')
        .attr('autocomplete', 'off');

      // add tinymce pattern
      $root.find('.mce_editable').addClass('pat-tinymce');

      // Use toggle to replace the toggleSelect from the select_all.js
      // First, remove the previous onclick
      $("[onclick^='toggleSelect']").attr('onclick', null);

      // Assign the class and data attributes for the "select all of the content_status_history template
      var select_all = $('form[action$=content_status_history] table.listing > thead tr th input[type=checkbox]');
      select_all.addClass('pat-toggle');
      select_all.attr({
        'data-pat-toggle': 'target: table.listing input[type=checkbox];attribute: checked;value: checked;externalClose: false;preventDefault: false'
      });

      // Assign the class and data attributes for the "select all of the usergroup-groupmembership view
      var select_all = $('form[action*=usergroup-groupmembership] table.listing tr th input[type=checkbox]');
      select_all.addClass('pat-toggle');
      select_all.attr({
        'data-pat-toggle': 'target: table.listing input[type=checkbox];attribute: checked;value: checked;externalClose: false;preventDefault: false'
      });

    },
    scan: function(selector) {
      registry.scan($(selector));
    }
  };

  registry.register(Widgets);

  return Widgets;
});
<commit_msg>Fixed the 'select_all' for the usergroup-usermembership view<commit_after>// Author: Rok Garbas
// Contact: rok@garbas.si
// Version: 1.0
// Description:
//
// License:
//
// Copyright (C) 2010 Plone Foundation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

/*jshint bitwise:true, curly:true, eqeqeq:true, immed:true, latedef:true,
  newcap:true, noarg:true, noempty:true, nonew:true, plusplus:true,
  undef:true, strict:true, trailing:true, browser:true, evil:true */
/*global define:false, window:false */

if (window.jQuery) {
  define( "jquery", [], function () {
    "use strict";
    return window.jQuery;
  } );
}

define([
  'jquery',
  'jam/Patterns/src/registry',
  'js/patterns/select2',
  'js/patterns/pickadate',
  'js/patterns/autotoc',
  'js/patterns/accessibility',
  'js/patterns/relateditems',
  'js/patterns/formUnloadAlert',
  'js/patterns/toggle',
  'js/patterns/tinymce',
  'js/patterns/picture',
  'js/patterns/livesearch',
  'js/patterns/querystring'
], function($, registry) {
  "use strict";

  // BBB: we need to hook pattern to classes which plone was using until now
  var Widgets = {
    name: "plone-widgets",
    transform: function($root) {

      // apply autotoc pattern where enableFormTabbing exists
      var $match = $root.filter('.enableFormTabbing');
      $match = $match.add($root.find('.enableFormTabbing'));
      $match.addClass('pat-autotoc');
      $match.attr({
        'data-pat-autotoc':'levels: legend;section: fieldset;klass: autotabs'
      });

      // activate accessibility pattern by default
      $root.addClass('pat-accessibility');
      $root.attr({
        'data-pat-accessibility': 'smallbtn: #accessibility-smallText;normalbtn: #accessibility-normalText;largebtn: #accessibility-largeText'
      });

      // apply formUnloadAlert pattern where enableUnloadProtection exists
      var $match = $root.filter('.enableUnloadProtection');
      $match = $match.add($root.find('.enableUnloadProtection'));
      $match.addClass('pat-formunloadalert');
      $match.attr({
        'data-pat-formunloadalert':'message: '+window.form_modified_message
      });

      // Fix drop downs
      var personal_tools = $('dl.actionMenu#portal-personaltools dt.actionMenuHeader a');
      personal_tools.addClass('pat-toggle');
      personal_tools.attr({
        'data-pat-toggle': 'target: dl.actionMenu#portal-personaltools;value: activated'
      });

      var add_new = $('dl.actionMenu#plone-contentmenu-factories dt.actionMenuHeader a');
      add_new.addClass('pat-toggle');
      add_new.attr({
        'data-pat-toggle': 'target: dl.actionMenu#plone-contentmenu-factories;value: activated'
      });

      var display = $('dl.actionMenu#plone-contentmenu-display dt.actionMenuHeader a');
      display.addClass('pat-toggle');
      display.attr({
        'data-pat-toggle': 'target: dl.actionMenu#plone-contentmenu-display;value: activated'
      });

      var workflow = $('dl.actionMenu#plone-contentmenu-workflow dt.actionMenuHeader a');
      workflow.addClass('pat-toggle');
      workflow.attr({
        'data-pat-toggle': 'target: dl.actionMenu#plone-contentmenu-workflow;value: activated'
      });

      $('dl.actionMenu').removeClass('deactivated');

      $('html').on('mousedown', function(e) {
        if (!$(e.toElement).parents('dl.actionMenu').hasClass('activated')){
          // Means we clicked an opened menu, do not close
          $('dl.actionMenu').removeClass('activated');
        }
      });

      // Live Search
      $match = $root.find('.LSBox');
      var url = $match.parents('form').attr('action').replace('@@search', '@@updated_search');
      var attrs = {
        'url': url,
        'results': {
          'content': '#search-results',
          'item': 'dt'
        }
      };
      $match.attr({
        'class': 'pat-livesearch',
        'data-pat-livesearch': JSON.stringify(attrs)
      });
      $match.find('.searchSection').remove();
      $match.find('.LSResult').attr({
        'class': 'pat-livesearch-container pull-right',
        'id': ''
      });
      $match.find('.LSShadow').attr('class', 'pat-livesearch-results');
      $match.find('#searchGadget').addClass('pat-livesearch-input')
        .attr('autocomplete', 'off');

      // add tinymce pattern
      $root.find('.mce_editable').addClass('pat-tinymce');

      // Use toggle to replace the toggleSelect from the select_all.js
      // First, remove the previous onclick
      $("[onclick^='toggleSelect']").attr('onclick', null);

      // Assign the class and data attributes for the "select all of the content_status_history template
      var select_all = $('form[action$=content_status_history] table.listing > thead tr th input[type=checkbox]');
      select_all.addClass('pat-toggle');
      select_all.attr({
        'data-pat-toggle': 'target: table.listing input[type=checkbox];attribute: checked;value: checked;externalClose: false;preventDefault: false'
      });

      // Assign the class and data attributes for the "select all of the usergroup-groupmembership view
      var select_all = $('form[action*=usergroup-groupmembership] table.listing tr th input[type=checkbox]');
      select_all.addClass('pat-toggle');
      select_all.attr({
        'data-pat-toggle': 'target: table.listing input[type=checkbox];attribute: checked;value: checked;externalClose: false;preventDefault: false'
      });

      // Assign the class and data attributes for the "select all of the usergroup-usermembership view
      var select_all = $('form[action*=usergroup-usermembership] table.listing tr th input[type=checkbox]');
      select_all.addClass('pat-toggle');
      select_all.attr({
        'data-pat-toggle': 'target:form[action*=usergroup-usermembership] table.listing:last input[type=checkbox];attribute: checked;value: checked;externalClose: false;preventDefault: false'
      });

    },
    scan: function(selector) {
      registry.scan($(selector));
    }
  };

  registry.register(Widgets);

  return Widgets;
});
<|endoftext|>


<commit_before>(function ($) {
    $.fn.mModal = function (options) {
        var settings = $.extend({
            modal: null,						// the modal itself
            contentUrl: '',						// load content from the given URL
            callbacks: [],						// button callbacks
            appearAnimation: 'fade',			// 'fade' or 'slide'
            disappearAnimation: 'fade',			// 'fade' or 'slide'
            appearSlideDirection: 'down',		// 'up', 'right', 'down' or 'left'
            disappearSlideDirection: 'up',		// 'up', 'right', 'down' or 'left'
            animateResize: true,				// animate to the new centre position instead of instant
            closeButton: true					// show a close button at the upper right corner
        }, options);

        if (settings.modal == null || typeof settings.modal == 'undefined') {
            console.error('The given modal was not set or found!');
            return false;
        }

        var modalLink = $(this);
        var modalContent = settings.modal.find('.mModal-content');
        var modalButtons = settings.modal.find('.mModal-buttons').children();
        var modalCloseButton = (settings.closeButton) ? $('

').appendTo(settings.modal) : $();
        var modalCover = null;

        settings.modal.off('click').click(function (e) {
            e.stopImmediatePropagation();
        }).hide();
        modalButtons.outerWidth(settings.modal.width() / modalButtons.length);

        settings.modal.openModal = function () {
            if (settings.contentUrl.length > 0) {
                var matches = settings.contentUrl.match(/(http(s)?:\/\/)?(www\.)?youtu(\.)?be(.+)?v=(.+)/i);
                if (matches != null) {
                    var youtubeIframe = $('').appendTo(modalContent).hide();
                    youtubeIframe.load(function () {
                        $(this).delay(100).fadeIn('slow');
                    });
                } else {
                    modalContent.load(settings.contentUrl);
                }

                modalContent.outerHeight(settings.modal.outerHeight());
                settings.modal.centerModal();
            }

            modalCover = settings.modal.wrap($('

')).parent();
            modalCover.off('click').click(function (e) {
                settings.modal.closeModal();
            }).hide();

            if (settings.appearAnimation == 'slide') {
                modalCover.fadeIn('fast', function () {
                    settings.modal.show();

                    var modalPositionLeft = ($(window).outerWidth() / 2) - (settings.modal.outerWidth() / 2);
                    var modalPositionTop = ($(window).outerHeight() / 2) - (settings.modal.outerHeight() / 2);

                    var startPositionLeft = (settings.appearSlideDirection == 'left' || settings.appearSlideDirection == 'right') ?
                        (settings.appearSlideDirection == 'left' ? ($(window).outerWidth()) : (-modalPositionLeft)) : modalPositionLeft;
                    var startPositionTop = (settings.appearSlideDirection == 'up' || settings.appearSlideDirection == 'down') ?
                        (settings.appearSlideDirection == 'up' ? ($(window).outerHeight()) : (-modalPositionTop)) : modalPositionTop;

                    settings.modal.css({left: startPositionLeft, top: startPositionTop});
                    settings.modal.stop(true, false).animate({left: modalPositionLeft, top: modalPositionTop});
                });
            } else {
                modalCover.fadeIn('fast');
                settings.modal.show();
            }

            modalButtons.off('click').click(function (e) {
                e.preventDefault();

                var modalButtonCallback = $(this).attr('data-mModalCallback');
                if (typeof modalButtonCallback == 'undefined') {
                    return false;
                }

                if (typeof settings.callbacks[modalButtonCallback] != 'undefined') {
                    settings.callbacks[modalButtonCallback](settings.modal);
                }
            });
        };

        settings.modal.centerModal = function () {
            var modalPositionLeft = ($(window).outerWidth() / 2) - (settings.modal.outerWidth() / 2);
            var modalPositionTop = ($(window).outerHeight() / 2) - (settings.modal.outerHeight() / 2);

            if (settings.animateResize == true) {
                settings.modal.stop(true, false).animate({
                    left: modalPositionLeft,
                    top: modalPositionTop
                }, 'fast', 'swing');
            } else {
                settings.modal.css({left: modalPositionLeft, top: modalPositionTop});
            }
        };

        settings.modal.closeModal = function () {
            if (settings.disappearAnimation == 'slide') {
                var modalPositionLeft = ($(window).outerWidth() / 2) - (settings.modal.outerWidth() / 2);
                var modalPositionTop = ($(window).outerHeight() / 2) - (settings.modal.outerHeight() / 2);

                var endPositionLeft = (settings.disappearSlideDirection == 'left' || settings.disappearSlideDirection == 'right') ?
                    (settings.disappearSlideDirection == 'left' ? (-modalPositionLeft) : ($(window).outerWidth())) : modalPositionLeft;
                var endPositionTop = (settings.disappearSlideDirection == 'up' || settings.disappearSlideDirection == 'down') ?
                    (settings.disappearSlideDirection == 'up' ? (-modalPositionTop) : ($(window).outerHeight())) : modalPositionTop;

                settings.modal.stop(true, false).animate({
                    left: endPositionLeft,
                    top: endPositionTop
                }, 'fast', 'swing', function () {
                    modalCover.fadeOut('fast', function () {
                        settings.modal.hide();
                        settings.modal.unwrap();

                        if (settings.contentUrl.length > 0) {
                            modalContent.empty();
                        }
                    });
                });
            }
            else {
                modalCover.fadeOut('fast', function () {
                    settings.modal.hide();
                    settings.modal.unwrap();

                    if (settings.contentUrl.length > 0) {
                        modalContent.empty();
                    }
                });
            }
        };

        modalLink.click(function (e) {
            e.preventDefault();
            settings.modal.openModal();
        });

        modalCloseButton.off('click').click(function (e) {
            e.preventDefault();
            settings.modal.closeModal();
        });

        $(window).resize(function (e) {
            settings.modal.centerModal();
        }).trigger('resize');
    };
}(jQuery));<commit_msg>Added option to disable close when clicking cover<commit_after>(function ($) {
    $.fn.mModal = function (options) {
        var settings = $.extend({
            modal: null,						// the modal itself
            contentUrl: '',						// load content from the given URL
            callbacks: [],						// button callbacks
            appearAnimation: 'fade',			// 'fade' or 'slide'
            disappearAnimation: 'fade',			// 'fade' or 'slide'
            appearSlideDirection: 'down',		// 'up', 'right', 'down' or 'left'
            disappearSlideDirection: 'up',		// 'up', 'right', 'down' or 'left'
            animateResize: true,				// animate to the new centre position instead of instant
            closeButton: true,					// show a close button at the upper right corner
            closeOnCoverClick: true             // close the modal if the cover is clicked
        }, options);

        if (settings.modal == null || typeof settings.modal == 'undefined') {
            console.error('The given modal was not set or found!');
            return false;
        }

        var modalLink = $(this);
        var modalContent = settings.modal.find('.mModal-content');
        var modalButtons = settings.modal.find('.mModal-buttons').children();
        var modalCloseButton = (settings.closeButton) ? $('

').appendTo(settings.modal) : $();
        var modalCover = null;

        settings.modal.off('click').click(function (e) {
            e.stopImmediatePropagation();
        }).hide();

        modalButtons.outerWidth(settings.modal.width() / modalButtons.length);

        settings.modal.openModal = function () {
            if (settings.contentUrl.length > 0) {
                var matches = settings.contentUrl.match(/(http(s)?:\/\/)?(www\.)?youtu(\.)?be(.+)?v=(.+)/i);
                if (matches != null) {
                    var youtubeIframe = $('').appendTo(modalContent).hide();
                    youtubeIframe.load(function () {
                        $(this).delay(100).fadeIn('slow');
                    });
                } else {
                    modalContent.load(settings.contentUrl);
                }

                modalContent.outerHeight(settings.modal.outerHeight());
                settings.modal.centerModal();
            }

            modalCover = settings.modal.wrap($('

')).parent();
            modalCover.off('click').click(function (e) {
                if (settings.closeOnCoverClick) {
                    settings.modal.closeModal();
                }
            }).hide();

            if (settings.appearAnimation == 'slide') {
                modalCover.fadeIn('fast', function () {
                    settings.modal.show();

                    var modalPositionLeft = ($(window).outerWidth() / 2) - (settings.modal.outerWidth() / 2);
                    var modalPositionTop = ($(window).outerHeight() / 2) - (settings.modal.outerHeight() / 2);

                    var startPositionLeft = (settings.appearSlideDirection == 'left' || settings.appearSlideDirection == 'right') ?
                        (settings.appearSlideDirection == 'left' ? ($(window).outerWidth()) : (-modalPositionLeft)) : modalPositionLeft;
                    var startPositionTop = (settings.appearSlideDirection == 'up' || settings.appearSlideDirection == 'down') ?
                        (settings.appearSlideDirection == 'up' ? ($(window).outerHeight()) : (-modalPositionTop)) : modalPositionTop;

                    settings.modal.css({left: startPositionLeft, top: startPositionTop});
                    settings.modal.stop(true, false).animate({left: modalPositionLeft, top: modalPositionTop});
                });
            } else {
                modalCover.fadeIn('fast');
                settings.modal.show();
            }

            modalButtons.off('click').click(function (e) {
                e.preventDefault();

                var modalButtonCallback = $(this).attr('data-mModalCallback');
                if (typeof modalButtonCallback == 'undefined') {
                    return false;
                }

                if (typeof settings.callbacks[modalButtonCallback] != 'undefined') {
                    settings.callbacks[modalButtonCallback](settings.modal);
                }
            });
        };

        settings.modal.centerModal = function () {
            var modalPositionLeft = ($(window).outerWidth() / 2) - (settings.modal.outerWidth() / 2);
            var modalPositionTop = ($(window).outerHeight() / 2) - (settings.modal.outerHeight() / 2);

            if (settings.animateResize == true) {
                settings.modal.stop(true, false).animate({
                    left: modalPositionLeft,
                    top: modalPositionTop
                }, 'fast', 'swing');
            } else {
                settings.modal.css({left: modalPositionLeft, top: modalPositionTop});
            }
        };

        settings.modal.closeModal = function () {
            if (settings.disappearAnimation == 'slide') {
                var modalPositionLeft = ($(window).outerWidth() / 2) - (settings.modal.outerWidth() / 2);
                var modalPositionTop = ($(window).outerHeight() / 2) - (settings.modal.outerHeight() / 2);

                var endPositionLeft = (settings.disappearSlideDirection == 'left' || settings.disappearSlideDirection == 'right') ?
                    (settings.disappearSlideDirection == 'left' ? (-modalPositionLeft) : ($(window).outerWidth())) : modalPositionLeft;
                var endPositionTop = (settings.disappearSlideDirection == 'up' || settings.disappearSlideDirection == 'down') ?
                    (settings.disappearSlideDirection == 'up' ? (-modalPositionTop) : ($(window).outerHeight())) : modalPositionTop;

                settings.modal.stop(true, false).animate({
                    left: endPositionLeft,
                    top: endPositionTop
                }, 'fast', 'swing', function () {
                    modalCover.fadeOut('fast', function () {
                        settings.modal.hide();
                        settings.modal.unwrap();

                        if (settings.contentUrl.length > 0) {
                            modalContent.empty();
                        }
                    });
                });
            }
            else {
                modalCover.fadeOut('fast', function () {
                    settings.modal.hide();
                    settings.modal.unwrap();

                    if (settings.contentUrl.length > 0) {
                        modalContent.empty();
                    }
                });
            }
        };

        modalLink.click(function (e) {
            e.preventDefault();
            settings.modal.openModal();
        });

        modalCloseButton.off('click').click(function (e) {
            e.preventDefault();
            settings.modal.closeModal();
        });

        $(window).resize(function (e) {
            settings.modal.centerModal();
        }).trigger('resize');
    };
}(jQuery));<|endoftext|>


<commit_before>'use strict'

var Lab = require('lab')
var Code = require('code')
var Proxyquire = require('proxyquire')
var NpmFakeData = require('./npm-data')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

var NpmProxy = {
  request: {
    get: (opts, done) => {
      if (opts.url.includes('seneca-entity')) {
        done(null, {}, JSON.stringify(NpmFakeData))
      }
      else {
        done(new Error('npm error'), null, null)
      }
    }
  }
}

var Seneca = Proxyquire('seneca', {})
var Npm = Proxyquire('..', NpmProxy)

function createInstance (done) {
  var params = {
    log: 'silent',
    strict: 'false',
    errhandler: (err) => {
      if (err.at) done(err)
    }
  }

  return Seneca(params)
    .use('entity')
    .use(Npm)
}

describe('A valid role:npm,cmd:get call', () => {
  it('Has no error and data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply).to.exist()
      done()
    })
  })

  it('Has id field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.id).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has name field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.name).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has urlPkg field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.urlPkg).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has urlRepo field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.urlRepo).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has description field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.description).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has latestVersion field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.latestVersion).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has releaseCount field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.releaseCount).to.exist().and.to.be.a.number()
      done()
    })
  })

  it('Has dependencies field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.dependencies).to.exist().and.to.be.an.object()
      done()
    })
  })

  it('Has author field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.author).to.exist().and.to.be.a.object()
      done()
    })
  })
  it('Has licence field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.licence).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has maintainers field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.maintainers).to.exist().and.to.be.an.array()
      done()
    })
  })

  it('Has readme field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.readme).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has homepage field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.homepage).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has cached field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.cached).to.exist().and.to.be.a.number()
      done()
    })
  })

  it('Returns cached data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
      expect(err).to.not.exist()
      var cachedOne = reply.cached
      seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
        expect(err).to.not.exist()
        var cachedTwo = reply.cached
        expect(cachedOne).to.equal(cachedTwo)
        done()
      })
    })
  })

  it('Can return non-cached data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
      expect(err).to.not.exist()
      var cachedOne = reply.cached
      payload.update = true

      seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
        expect(err).to.not.exist()
        var cachedTwo = reply.cached
        expect(cachedOne).to.be.below(cachedTwo)
        done()
      })
    })
  })
})

describe('An invalid role:npm,cmd:get call', () => {
  it('Has an error and no data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'randomName0927e3'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.exist()
      expect(reply).to.not.exist()
      done()
    })
  })
})

describe('A valid role:info,req:part call', () => {
  it('Has no error and has data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act(`role:info,req:part`, payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply).to.exist()
      done()
    })
  })

  it('Responds via role:info,res:part', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.add(`role:info,res:part`, (msg, cb) => {
      expect(msg).to.exist()
      cb()
      done()
    })

    seneca.act(`role:info,req:part`, payload, function (err, reply) {
      expect(err).to.not.exist()
      expect(reply).to.exist()
    })
  })
})
<commit_msg>nodezoo-npm Issue #54<commit_after>'use strict'

var Lab = require('lab')
var Code = require('code')
var Proxyquire = require('proxyquire')
var NpmFakeData = require('./npm-data')

var lab = exports.lab = Lab.script()
var describe = lab.describe
var it = lab.it
var expect = Code.expect

process.setMaxListeners(20)

var NpmProxy = {
  request: {
    get: (opts, done) => {
      if (opts.url.includes('seneca-entity')) {
        done(null, {}, JSON.stringify(NpmFakeData))
      }
      else {
        done(new Error('npm error'), null, null)
      }
    }
  }
}

var Seneca = Proxyquire('seneca', {})
var Npm = Proxyquire('..', NpmProxy)

function createInstance (done) {
  var params = {
    log: 'silent',
    strict: 'false',
    errhandler: (err) => {
      if (err.at) done(err)
    }
  }

  return Seneca(params)
    .use('entity')
    .use(Npm)
}

describe('A valid role:npm,cmd:get call', () => {
  it('Has no error and data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply).to.exist()
      done()
    })
  })

  it('Has id field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.id).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has name field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.name).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has urlPkg field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.urlPkg).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has urlRepo field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.urlRepo).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has description field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.description).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has latestVersion field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.latestVersion).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has releaseCount field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.releaseCount).to.exist().and.to.be.a.number()
      done()
    })
  })

  it('Has dependencies field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.dependencies).to.exist().and.to.be.an.object()
      done()
    })
  })

  it('Has author field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.author).to.exist().and.to.be.a.object()
      done()
    })
  })
  it('Has licence field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.licence).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has maintainers field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.maintainers).to.exist().and.to.be.an.array()
      done()
    })
  })

  it('Has readme field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.readme).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has homepage field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.homepage).to.exist().and.to.be.a.string()
      done()
    })
  })

  it('Has cached field', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply.cached).to.exist().and.to.be.a.number()
      done()
    })
  })

  it('Returns cached data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
      expect(err).to.not.exist()
      var cachedOne = reply.cached
      seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
        expect(err).to.not.exist()
        var cachedTwo = reply.cached
        expect(cachedOne).to.equal(cachedTwo)
        done()
      })
    })
  })

  it('Can return non-cached data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
      expect(err).to.not.exist()
      var cachedOne = reply.cached
      payload.update = true

      seneca.act(`role:npm,cmd:get`, payload, (err, reply) => {
        expect(err).to.not.exist()
        var cachedTwo = reply.cached
        expect(cachedOne).to.be.below(cachedTwo)
        done()
      })
    })
  })
})

describe('An invalid role:npm,cmd:get call', () => {
  it('Has an error and no data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'randomName0927e3'}

    seneca.act('role:npm,cmd:get', payload, (err, reply) => {
      expect(err).to.exist()
      expect(reply).to.not.exist()
      done()
    })
  })
})

describe('A valid role:info,req:part call', () => {
  it('Has no error and has data', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.act(`role:info,req:part`, payload, (err, reply) => {
      expect(err).to.not.exist()
      expect(reply).to.exist()
      done()
    })
  })

  it('Responds via role:info,res:part', (done) => {
    var seneca = createInstance(done)
    var payload = {name: 'seneca-entity'}

    seneca.add(`role:info,res:part`, (msg, cb) => {
      expect(msg).to.exist()
      cb()
      done()
    })

    seneca.act(`role:info,req:part`, payload, function (err, reply) {
      expect(err).to.not.exist()
      expect(reply).to.exist()
    })
  })
})
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.m.DynamicPageHeader.
sap.ui.define(["jquery.sap.global", "./library", "sap/ui/core/Control", "sap/ui/core/InvisibleText", "./ButtonType", "./ToggleButton"],
	function (jQuery, library, Control, InvisibleText, ButtonType, ToggleButton) {
		"use strict";

		/**
		 * Constructor for a new Basic Page Layout Header.
		 *
		 * @param {string} [sId] id for the new control, generated automatically if no id is given
		 * @param {object} [mSettings] initial settings for the new control
		 *
		 * @class
		 * The DynamicPage header can hold any UI5 layout control. The header has two versions - snapped and expanded.
		 * The header switches between these modes when the user scrolls below its bottom margin.
		 * Disclaimer: this control is in beta state - incompatible API changes may be done before its official public release. Use at your own discretion.
		 *
		 * @author SAP SE
		 * @version ${version}
		 *
		 * @constructor
		 * @private
		 * @since 1.38
		 * @alias sap.m.DynamicPageHeader
		 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
		 */
		var DynamicPageHeader = Control.extend("sap.m.DynamicPageHeader", /** @lends sap.m.DynamicPageHeader.prototype */ {
			metadata: {
				interfaces: [
					"sap.m.ISnappable"
				],
				library: "sap.m",
				properties: {
					/**
					 * Determines whether the header will be pinnable.
					 */
					pinnable: {type: "boolean", group: "Appearance", defaultValue: true}
				},
				aggregations: {

					/**
					 * The content of the header.
					 */
					content: {type: "sap.ui.core.Control", multiple: true},

					/**
					 *  The pin/unpin button in the header.
					 */
					_pinButton: {type: "sap.m.Button", multiple: false, visibility: "hidden"}
				}
			}
		});

		/*************************************** Static members ******************************************/

		DynamicPageHeader._LIBRARY = "sap.m";

		/**
		 * Retrieves the resource bundle for the sap.m library
		 *
		 * @returns {Object} the resource bundle object
		 */
		DynamicPageHeader._getResourceBundle = function () {
			return sap.ui.getCore().getLibraryResourceBundle(DynamicPageHeader._LIBRARY);
		};

		DynamicPageHeader.ARIA = {
			ARIA_CONTROLS: "aria-controls",
			ARIA_EXPANDED: "aria-expanded",
			ARIA_LABEL: "aria-label",
			LABEL_EXPANDED: DynamicPageHeader._getResourceBundle().getText("EXPANDED_HEADER"),
			LABEL_SNAPPED: DynamicPageHeader._getResourceBundle().getText("SNAPPED_HEADER"),
			LABEL_PINNED: DynamicPageHeader._getResourceBundle().getText("PIN_HEADER"),
			LABEL_UNPINNED: DynamicPageHeader._getResourceBundle().getText("UNPIN_HEADER"),
			STATE_TRUE: "true",
			STATE_FALSE: "false"
		};

		/*************************************** Lifecycle members ******************************************/
		DynamicPageHeader.prototype.onBeforeRendering = function () {
			if (this.getPinnable()) {
				this._getPinButton().addAriaLabelledBy(this._getARIAInvisibleText());
			}
		};

		DynamicPageHeader.prototype.onAfterRendering = function () {
			this._initARIAState();
			this._initPinButtonARIAState();
		};

		/*************************************** Private members ******************************************/

		/**
		 * Determines the pin/unpin toggle button state
		 * @param bValue
		 * @private
		 */
		DynamicPageHeader.prototype._togglePinButton = function (bValue) {
			this._getPinButton().setPressed(bValue);
		};

		/**
		 * Used to internally show/hide the pin/unpin button without going through the rendering phase
		 * @param {boolean} bValue - to show or to hide the button
		 * @private
		 */
		DynamicPageHeader.prototype._setShowPinBtn = function (bValue) {
			this._getPinButton().$().toggleClass("sapUiHidden", !bValue);
		};

		/**
		 * Fires the pin/unpin press event
		 * @private
		 */
		DynamicPageHeader.prototype._pinUnpinFireEvent = function () {
			this.fireEvent("_pinUnpinPress");
		};

		/**
		 * Initializes the Header ARIA State.
		 * @private
		 */
		DynamicPageHeader.prototype._initARIAState = function () {
			var $header = this.$();

			$header.attr(DynamicPageHeader.ARIA.ARIA_EXPANDED, DynamicPageHeader.ARIA.STATE_TRUE);
			$header.attr(DynamicPageHeader.ARIA.ARIA_LABEL, DynamicPageHeader.ARIA.LABEL_EXPANDED);
		};

		/**
		 * Initializes the Header Pin Button ARIA State.
		 * @private
		 */
		DynamicPageHeader.prototype._initPinButtonARIAState = function () {
			var $pinButton;

			if (this.getPinnable()) {
				$pinButton = this._getPinButtonJQueryRef();
				$pinButton.attr(DynamicPageHeader.ARIA.ARIA_CONTROLS, this.getId());
			}
		};

		/**
		 * Updates Header ARIA attributes values according to expanded/snapped state.
		 * @param {Boolean} bExpanded determines if the Header is expanded or snapped
		 * @private
		 */
		DynamicPageHeader.prototype._updateARIAState = function (bExpanded) {
			var $header = this.$();

			if (bExpanded) {
				$header.attr(DynamicPageHeader.ARIA.ARIA_EXPANDED, DynamicPageHeader.ARIA.STATE_TRUE);
				$header.attr(DynamicPageHeader.ARIA.ARIA_LABEL, DynamicPageHeader.ARIA.LABEL_EXPANDED);
			} else {
				$header.attr(DynamicPageHeader.ARIA.ARIA_EXPANDED, DynamicPageHeader.ARIA.STATE_FALSE);
				$header.attr(DynamicPageHeader.ARIA.ARIA_LABEL, DynamicPageHeader.ARIA.LABEL_SNAPPED);
			}
		};

		/**
		 * Updates Header Pin Button ARIA attributes values according to pinned/unpinned state.
		 * @param {Boolean} bPinned determines if the Header is pinned or unpinned
		 * @private
		 */
		DynamicPageHeader.prototype._updateARIAPinButtonState = function (bPinned) {
			var oInvisibleText = this._getARIAInvisibleText();

			if (bPinned) {
				oInvisibleText.setText(DynamicPageHeader.ARIA.LABEL_UNPINNED);
			} else {
				oInvisibleText.setText(DynamicPageHeader.ARIA.LABEL_PINNED);
			}
		};

		/**
		 * Lazily loads ARIA sap.ui.core.InvisibleText for the given translation text
		 *
		 * @param {String} sARIAText the ARIA announcement text
		 * @returns {Object} the InvisibleText control
		 * @private
		 */
		DynamicPageHeader.prototype._getARIAInvisibleText = function () {
			if (!this._oInvisibleText) {
				this._oInvisibleText = new InvisibleText({
					text: DynamicPageHeader.ARIA.LABEL_PINNED
				}).toStatic();
			}

			return this._oInvisibleText;
		};

		/**
		 * Lazily retrieves the Header Pin button.
		 * @returns {sap.m.ToggleButton}
		 * @private
		 */
		DynamicPageHeader.prototype._getPinButton = function () {
			if (!this.getAggregation("_pinButton")) {
				var oPinButton = new ToggleButton({
					id: this.getId() + "-pinBtn",
					icon: "sap-icon://pushpin-off",
					type: ButtonType.Transparent,
					press: this._pinUnpinFireEvent.bind(this)
				});
				this.setAggregation("_pinButton", oPinButton, true);
			}

			return this.getAggregation("_pinButton");
		};

		/**
		 * Focuses the Header Pin button.
		 * @private
		 */
		DynamicPageHeader.prototype._focusPinButton = function () {
			this._getPinButtonJQueryRef().focus();
		};

		/**
		 * Returns the Header Pin Button DOM Ref.
		 * @return {jQuery}
		 * @private
		 */
		DynamicPageHeader.prototype._getPinButtonJQueryRef = function () {
			return this._getPinButton().$();
		};

		return DynamicPageHeader;

	}, /* bExport= */ false);
<commit_msg>[INTERNAL][FIX] m.DynamicPageHeader: tooltip for pin btn provided<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.m.DynamicPageHeader.
sap.ui.define(["jquery.sap.global", "./library", "sap/ui/core/Control", "sap/ui/core/InvisibleText", "./ButtonType", "./ToggleButton"],
	function (jQuery, library, Control, InvisibleText, ButtonType, ToggleButton) {
		"use strict";

		/**
		 * Constructor for a new Basic Page Layout Header.
		 *
		 * @param {string} [sId] id for the new control, generated automatically if no id is given
		 * @param {object} [mSettings] initial settings for the new control
		 *
		 * @class
		 * The DynamicPage header can hold any UI5 layout control. The header has two versions - snapped and expanded.
		 * The header switches between these modes when the user scrolls below its bottom margin.
		 * Disclaimer: this control is in beta state - incompatible API changes may be done before its official public release. Use at your own discretion.
		 *
		 * @author SAP SE
		 * @version ${version}
		 *
		 * @constructor
		 * @private
		 * @since 1.38
		 * @alias sap.m.DynamicPageHeader
		 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
		 */
		var DynamicPageHeader = Control.extend("sap.m.DynamicPageHeader", /** @lends sap.m.DynamicPageHeader.prototype */ {
			metadata: {
				interfaces: [
					"sap.m.ISnappable"
				],
				library: "sap.m",
				properties: {
					/**
					 * Determines whether the header will be pinnable.
					 */
					pinnable: {type: "boolean", group: "Appearance", defaultValue: true}
				},
				aggregations: {

					/**
					 * The content of the header.
					 */
					content: {type: "sap.ui.core.Control", multiple: true},

					/**
					 *  The pin/unpin button in the header.
					 */
					_pinButton: {type: "sap.m.Button", multiple: false, visibility: "hidden"}
				}
			}
		});

		/*************************************** Static members ******************************************/

		DynamicPageHeader._LIBRARY = "sap.m";

		/**
		 * Retrieves the resource bundle for the sap.m library
		 *
		 * @returns {Object} the resource bundle object
		 */
		DynamicPageHeader._getResourceBundle = function () {
			return sap.ui.getCore().getLibraryResourceBundle(DynamicPageHeader._LIBRARY);
		};

		DynamicPageHeader.ARIA = {
			ARIA_CONTROLS: "aria-controls",
			ARIA_EXPANDED: "aria-expanded",
			ARIA_LABEL: "aria-label",
			LABEL_EXPANDED: DynamicPageHeader._getResourceBundle().getText("EXPANDED_HEADER"),
			LABEL_SNAPPED: DynamicPageHeader._getResourceBundle().getText("SNAPPED_HEADER"),
			LABEL_PINNED: DynamicPageHeader._getResourceBundle().getText("PIN_HEADER"),
			LABEL_UNPINNED: DynamicPageHeader._getResourceBundle().getText("UNPIN_HEADER"),
			STATE_TRUE: "true",
			STATE_FALSE: "false"
		};

		/*************************************** Lifecycle members ******************************************/
		DynamicPageHeader.prototype.onAfterRendering = function () {
			this._initARIAState();
			this._initPinButtonARIAState();
		};

		/*************************************** Private members ******************************************/

		/**
		 * Determines the pin/unpin toggle button state
		 * @param bValue
		 * @private
		 */
		DynamicPageHeader.prototype._togglePinButton = function (bValue) {
			this._getPinButton().setPressed(bValue);
		};

		/**
		 * Used to internally show/hide the pin/unpin button without going through the rendering phase
		 * @param {boolean} bValue - to show or to hide the button
		 * @private
		 */
		DynamicPageHeader.prototype._setShowPinBtn = function (bValue) {
			this._getPinButton().$().toggleClass("sapUiHidden", !bValue);
		};

		/**
		 * Fires the pin/unpin press event
		 * @private
		 */
		DynamicPageHeader.prototype._pinUnpinFireEvent = function () {
			this.fireEvent("_pinUnpinPress");
		};

		/**
		 * Initializes the Header ARIA State.
		 * @private
		 */
		DynamicPageHeader.prototype._initARIAState = function () {
			var $header = this.$();

			$header.attr(DynamicPageHeader.ARIA.ARIA_EXPANDED, DynamicPageHeader.ARIA.STATE_TRUE);
			$header.attr(DynamicPageHeader.ARIA.ARIA_LABEL, DynamicPageHeader.ARIA.LABEL_EXPANDED);
		};

		/**
		 * Initializes the Header Pin Button ARIA State.
		 * @private
		 */
		DynamicPageHeader.prototype._initPinButtonARIAState = function () {
			var $pinButton;

			if (this.getPinnable()) {
				$pinButton = this._getPinButtonJQueryRef();
				$pinButton.attr(DynamicPageHeader.ARIA.ARIA_CONTROLS, this.getId());
			}
		};

		/**
		 * Updates Header ARIA attributes values according to expanded/snapped state.
		 * @param {Boolean} bExpanded determines if the Header is expanded or snapped
		 * @private
		 */
		DynamicPageHeader.prototype._updateARIAState = function (bExpanded) {
			var $header = this.$();

			if (bExpanded) {
				$header.attr(DynamicPageHeader.ARIA.ARIA_EXPANDED, DynamicPageHeader.ARIA.STATE_TRUE);
				$header.attr(DynamicPageHeader.ARIA.ARIA_LABEL, DynamicPageHeader.ARIA.LABEL_EXPANDED);
			} else {
				$header.attr(DynamicPageHeader.ARIA.ARIA_EXPANDED, DynamicPageHeader.ARIA.STATE_FALSE);
				$header.attr(DynamicPageHeader.ARIA.ARIA_LABEL, DynamicPageHeader.ARIA.LABEL_SNAPPED);
			}
		};

		/**
		 * Updates Header Pin Button ARIA attributes values according to pinned/unpinned state.
		 * @param {Boolean} bPinned determines if the Header is pinned or unpinned
		 * @private
		 */
		DynamicPageHeader.prototype._updateARIAPinButtonState = function (bPinned) {
			var oPinBtn = this._getPinButton();

			if (bPinned) {
				oPinBtn.setTooltip(DynamicPageHeader.ARIA.LABEL_UNPINNED);
			} else {
				oPinBtn.setTooltip(DynamicPageHeader.ARIA.LABEL_PINNED);
			}
		};

		/**
		 * Lazily retrieves the Header Pin button.
		 * @returns {sap.m.ToggleButton}
		 * @private
		 */
		DynamicPageHeader.prototype._getPinButton = function () {
			if (!this.getAggregation("_pinButton")) {
				var oPinButton = new ToggleButton({
					id: this.getId() + "-pinBtn",
					icon: "sap-icon://pushpin-off",
					tooltip: DynamicPageHeader.ARIA.LABEL_PINNED,
					type: ButtonType.Transparent,
					press: this._pinUnpinFireEvent.bind(this)
				});
				this.setAggregation("_pinButton", oPinButton, true);
			}

			return this.getAggregation("_pinButton");
		};

		/**
		 * Focuses the Header Pin button.
		 * @private
		 */
		DynamicPageHeader.prototype._focusPinButton = function () {
			this._getPinButtonJQueryRef().focus();
		};

		/**
		 * Returns the Header Pin Button DOM Ref.
		 * @return {jQuery}
		 * @private
		 */
		DynamicPageHeader.prototype._getPinButtonJQueryRef = function () {
			return this._getPinButton().$();
		};

		return DynamicPageHeader;

	}, /* bExport= */ false);
<|endoftext|>


<commit_before>import React from 'react'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'
import { goToTop, goToAnchor } from 'react-scrollable-anchor'

import { Divider, Grid, Header } from 'semantic-ui-react'

import * as accountActions from '../actions/accountActions'
import * as postActions from '../actions/postActions'
import * as preferenceActions from '../actions/preferenceActions'
import * as statusActions from '../actions/statusActions'

import Post from '../components/elements/post'
import Response from '../components/elements/response'
import Paginator from '../components/global/paginator'

class Thread extends React.Component {

  componentWillMount() {
    this.fetchPost()
  }

  fetchPost() {
    this.props.actions.resetPostState()
    this.props.actions.fetchPost(this.props.match.params)
    this.props.actions.fetchPostResponses(this.props.match.params)
    if (!this.props.account) {
      this.props.actions.fetchAccount()
    }
  }

  scrollToPost = (id) => {
    let page = this.getPageForPost(id)
    // Couldn't determine page, goto last and bottom
    if(page === false) {
      let total = this.props.post.responses.length,
          perPage = this.props.preferences.threadPostsPerPage
      page = Math.ceil(total / perPage)
    }
    // Change page and focus
    this.changePage(page, id)
  }

  getPageForPost = (id) => {
    let collection = this.props.post.responses,
        perPage = this.props.preferences.threadPostsPerPage,
        position = false
    for(var i = 0; i < collection.length; i++) {
      if(collection[i]['_id'] === id) {
        position = i
      }
    }
    if(position === false) return position
    return Math.floor(position / perPage) + 1;
  }

  changePage = (page, scrollTo = false) => {
    let state = {
      page: page
    }
    if(scrollTo) {
      state.scrollTo = scrollTo
    } else {
      goToTop()
    }
    this.setState(state)
  }

  componentDidUpdate() {
    const anchor = (this.state) ? this.state.scrollTo : false
    if(this.state && this.state.scrollTo && this.props.post.responses.length > 0) {
      this.setState({scrollTo: false})
      setTimeout(function() {
        goToAnchor(anchor)
      }, 50)
    }
    if(this.state && this.state.scrollToWhenReady && this.props.post.responses.length > 0) {
      this.scrollToPost(this.state.scrollToWhenReady)
      this.setState({
        scrollToWhenReady: false
      })
    }
    if(this.props.match.url !== this.props.post.content.url) {
      this.fetchPost();
    }
  }

  componentDidMount() {
    const { hash } = location;
    const regexPage = /#comments-page-(\d+)+$/
    const regexPost = /#@?([A-Za-z0-9\-_]+)\/([A-Za-z0-9\-_]+)$/
    let matchesPage = hash.match(regexPage)
    let matchesPost = hash.match(regexPost)
    if(matchesPage) {
      this.setState({
        page: parseInt(matchesPage[1], 10)
      })
    }
    if(matchesPost) {
      let anchor = matchesPost[1] + '/' + matchesPost[2]
      this.setState({
        scrollToWhenReady: anchor
      })
    }
  }

  render() {
    let page = (this.state) ? this.state.page : 1,
        perPage = this.props.preferences.threadPostsPerPage,
        responses = (this.props.post) ? this.props.post.responses : 0,
        pages = Math.ceil(responses.length / perPage)
    let comments_nav = (
      <Grid id={(page ? `comments-page-${page}` : '')}>
        <Grid.Row verticalAlign='middle'>
          <Grid.Column only='tablet computer' width={4}>

              Comments ({responses.length})

                Page {page} of {pages}

          </Grid.Column>
          <Grid.Column mobile={16} tablet={12} computer={12}>
            <Paginator
              page={page}
              perPage={perPage}
              total={responses.length}
              callback={this.changePage}
              />
          </Grid.Column>
        </Grid.Row>
      </Grid>
    )
    return (

        <Post
          page={page}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props }/>
        <Divider></Divider>
        { comments_nav }
        <Divider horizontal>Page {page}</Divider>
        <Response
          page={page}
          perPage={perPage}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props } />
        <Divider horizontal id='comments-new'>Page {page}</Divider>
        { comments_nav }

    )
  }
}

function mapStateToProps(state, ownProps) {
  return {
    post: state.post,
    preferences: state.preferences
  }
}

function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators({
    ...accountActions,
    ...postActions,
    ...preferenceActions,
    ...statusActions
  }, dispatch)}
}

export default connect(mapStateToProps, mapDispatchToProps)(Thread);
<commit_msg>Passing the status props to the thread container for response404.js<commit_after>import React from 'react'
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'
import { goToTop, goToAnchor } from 'react-scrollable-anchor'

import { Divider, Grid, Header } from 'semantic-ui-react'

import * as accountActions from '../actions/accountActions'
import * as postActions from '../actions/postActions'
import * as preferenceActions from '../actions/preferenceActions'
import * as statusActions from '../actions/statusActions'

import Post from '../components/elements/post'
import Response from '../components/elements/response'
import Paginator from '../components/global/paginator'

class Thread extends React.Component {

  componentWillMount() {
    this.fetchPost()
  }

  fetchPost() {
    this.props.actions.resetPostState()
    this.props.actions.fetchPost(this.props.match.params)
    this.props.actions.fetchPostResponses(this.props.match.params)
    if (!this.props.account) {
      this.props.actions.fetchAccount()
    }
  }

  scrollToPost = (id) => {
    let page = this.getPageForPost(id)
    // Couldn't determine page, goto last and bottom
    if(page === false) {
      let total = this.props.post.responses.length,
          perPage = this.props.preferences.threadPostsPerPage
      page = Math.ceil(total / perPage)
    }
    // Change page and focus
    this.changePage(page, id)
  }

  getPageForPost = (id) => {
    let collection = this.props.post.responses,
        perPage = this.props.preferences.threadPostsPerPage,
        position = false
    for(var i = 0; i < collection.length; i++) {
      if(collection[i]['_id'] === id) {
        position = i
      }
    }
    if(position === false) return position
    return Math.floor(position / perPage) + 1;
  }

  changePage = (page, scrollTo = false) => {
    let state = {
      page: page
    }
    if(scrollTo) {
      state.scrollTo = scrollTo
    } else {
      goToTop()
    }
    this.setState(state)
  }

  componentDidUpdate() {
    const anchor = (this.state) ? this.state.scrollTo : false
    if(this.state && this.state.scrollTo && this.props.post.responses.length > 0) {
      this.setState({scrollTo: false})
      setTimeout(function() {
        goToAnchor(anchor)
      }, 50)
    }
    if(this.state && this.state.scrollToWhenReady && this.props.post.responses.length > 0) {
      this.scrollToPost(this.state.scrollToWhenReady)
      this.setState({
        scrollToWhenReady: false
      })
    }
    if(this.props.match.url !== this.props.post.content.url) {
      this.fetchPost();
    }
  }

  componentDidMount() {
    const { hash } = location;
    const regexPage = /#comments-page-(\d+)+$/
    const regexPost = /#@?([A-Za-z0-9\-_]+)\/([A-Za-z0-9\-_]+)$/
    let matchesPage = hash.match(regexPage)
    let matchesPost = hash.match(regexPost)
    if(matchesPage) {
      this.setState({
        page: parseInt(matchesPage[1], 10)
      })
    }
    if(matchesPost) {
      let anchor = matchesPost[1] + '/' + matchesPost[2]
      this.setState({
        scrollToWhenReady: anchor
      })
    }
  }

  render() {
    let page = (this.state) ? this.state.page : 1,
        perPage = this.props.preferences.threadPostsPerPage,
        responses = (this.props.post) ? this.props.post.responses : 0,
        pages = Math.ceil(responses.length / perPage)
    let comments_nav = (
      <Grid id={(page ? `comments-page-${page}` : '')}>
        <Grid.Row verticalAlign='middle'>
          <Grid.Column only='tablet computer' width={4}>

              Comments ({responses.length})

                Page {page} of {pages}

          </Grid.Column>
          <Grid.Column mobile={16} tablet={12} computer={12}>
            <Paginator
              page={page}
              perPage={perPage}
              total={responses.length}
              callback={this.changePage}
              />
          </Grid.Column>
        </Grid.Row>
      </Grid>
    )
    return (

        <Post
          page={page}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props }/>
        <Divider></Divider>
        { comments_nav }
        <Divider horizontal>Page {page}</Divider>
        <Response
          page={page}
          perPage={perPage}
          changePage={this.changePage}
          scrollToPost={this.scrollToPost}
          { ...this.props } />
        <Divider horizontal id='comments-new'>Page {page}</Divider>
        { comments_nav }

    )
  }
}

function mapStateToProps(state, ownProps) {
  return {
    post: state.post,
    preferences: state.preferences,
    status: state.status
  }
}

function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators({
    ...accountActions,
    ...postActions,
    ...preferenceActions,
    ...statusActions
  }, dispatch)}
}

export default connect(mapStateToProps, mapDispatchToProps)(Thread);
<|endoftext|>


<commit_before>/**
 * Created by Lynn on 2017/6/26.
 * 验证器
 * 参数格式
 * { 'key' : [?{valid:String,handle:Function|null,arg:Function|String,message:String|Function,watch:Boolean}]? }
 */
import _ from 'lodash'
const validators = { // 结果真代表通过
  required: (val) => { // 必填
    let res = false
    if (Array.isArray(val)) {
      res = val.length > 0
    } else if (typeof val === 'number') {
      res = true
    } else if (typeof val === 'boolean') {
      res = val
    } else if (typeof val === 'string') {
      res = val.length > 0
    } else if (val !== null && typeof val === 'object') {
      res = Object.keys(val).length > 0
    } else if (val === null || val === undefined) {
      res = false
    }
    return res
  },

  numeric: (val = '') => { // 数字
    const res = (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(val)
    return res
  },

  integer: (val = '') => { // 整数
    const res = (/^(-?[1-9]\d*|0)$/).test(val)
    return res
  },

  positive: (val = '') => { // 正整数
    const res = (/^\+?[1-9][0-9]*$/).test(val)
    return res
  },

  digits: (val = '') => { // 小数
    const res = (/^[\d() .:\-+#]+$/).test(val)
    return res
  },

  alpha: (val = '') => { // 字母
    const res = (/^[a-zA-Z]+$/).test(val)
    return res
  },

  alphaNum: (val = '') => { // 数字或者字母
    const res = (/^[a-zA-Z0-9]+$/).test(val)
    return res
  },

  email: (val = '') => { // email
    const res = (/^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i).test(val)
    return res
  },

  phone: (val = '') => { // 手机号
    const res = !!val.match(/^(0|86|17951)?(13|15|17|18|14)[0-9]{9}$/)
    return res
  },

  url: (val = '') => { // url地址
    const res = (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(val)
    return res
  },

  minLength: (val, arg) => { // 最大长度
    const res = val && val.length && val.length >= +arg
    return res
  },

  maxLength: (val = '', arg) => { // 最大长度
    const res = val.length <= +arg
    return res
  },

  minMaxLength: (val = '', arg) => { // 最大长度
    const max = val.length <= +arg.max
    const min = val.length >= +arg.min
    return min && max
  },

  length: (val, arg) => { // 长度
    const res = val && val.length === +arg
    return res
  },

  min: (val, arg) => { // 最小
    const res = val >= +arg
    return res
  },

  max: (val, arg) => { // 最大
    const res = val <= +arg
    return res
  },

  pattern: (val, arg) => { // 匹配规则
    const match = arg.match(new RegExp('^/(.*?)/([gimy]*)$'))
    const regex = new RegExp(match[1], match[2])
    const res = regex.test(val)
    return res
  },
  domain: (val) => {
    const regex = /^[a-z0-9A-Z\u4E00-\u9FA5][-a-zA-Z0-9\u4E00-\u9FA5]{0,62}(.[a-zA-Z0-9\u4E00-\u9FA5][-a-zA-Z0-9\u4E00-\u9FA5]{0,62})*(.[a-zA-Z\u4E00-\u9FA5]+)$/
    const res = regex.test(val)
    return res
  },
  ip: (val) => {
    const regex = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/
    const res = regex.test(val)
    return res
  },
  name: (val = '') => { // 中英文数字以及 - _ .
    const regex = new RegExp('^[A-Za-z0-9\u4e00-\u9fa5-_.]+$', 'g')
    const res = regex.test(val)
    return res
  }
}
const defaultValid = {
  '$init': true, // 原始
  '$valid': undefined, // 有效
  '$run': false, // 检查中
  '$error': {} // 错误列表
}
const validator = {
  created () {
    this.$setValidation()
  },
  data () {
    return {
      valid$: Object.assign({}, defaultValid)
    }
  },
  computed: {
    $validation () {
      const self = this
      if (!_.isObject(self.$$validation)) {
        return {}
      }
      return {
        getValidSet(key) {
          const $$validOptions = self.$$validation[key]
          let $$validSet = []
          if (!_.isArray($$validOptions) && !_.isObject($$validOptions)) {
            return $$validSet
          }
          if (_.isObject($$validOptions)) {
            $$validSet = [$$validOptions]
          }
          if (_.isArray($$validOptions)) {
            $$validSet = $$validOptions
          }
          return $$validSet
        },
        reset() {
          return new Promise(function (resolve, reject) {
            try {
              self.valid$ = Object.assign({}, defaultValid)
              self.$nextTick(() => {
                resolve.call(self)
              })
            } catch (e) {
              reject(e)
            }
          })
        },
        valid: (valid) => {
          return new Promise(function (resolve, reject) {
            if (!valid) {
              Object.keys(self.$$validation).forEach(validOne)
            } else {
              validOne(valid)
            }
            function validOne (valid) {
              const value = _.get(self, valid, null)
              const $$validSet = self.$validation.getValidSet(valid)
              if ($$validSet.length <= 0) {
                return
              }
              function changeBefore$Valid () {
                self.valid$[valid]['$init'] = false
                self.valid$[valid]['$run'] = true
                self.valid$[valid]['$error'] = {}
                self.valid$['$init'] = false
                delete self.valid$['$error'][valid]
              }

              function change$ValidIng (validResult, validKey, message) {
                if (validResult === false) {
                  self.valid$[valid]['$error'][validKey] = message
                  self.valid$['$error'][valid] = self.valid$['$error'][valid] || {}
                  self.valid$['$error'][valid][validKey] = message
                  self.valid$[valid]['$firstError'] || (self.valid$[valid]['$firstError'] = message)
                }
                self.valid$[valid]['$run'] = false
              }

              function changeAfter$Valid () {
                let $validOne = Object.keys(self.valid$[valid]['$error']).length <= 0
                let $valid = Object.keys(self.valid$['$error']).length <= 0
                let $run = false
                Object.keys(self.valid$).forEach(function (v) {
                  if (_.isObject(self.valid$[v]) && self.valid$[v]['$run'] === true) {
                    $run = true
                    return
                  }
                })
                self.valid$[valid]['$valid'] = $validOne
                self.valid$['$valid'] = $valid
                self.valid$['$run'] = $run
              }

              function createPromiseSet (set) {
                changeBefore$Valid()
                const validPromiseSet = []
                set.forEach(optionsHandle)
                function optionsHandle (options) {
                  if (!_.isObject(options)) {
                    return
                  }
                  let validHandle = () => {}
                  const validKey = options.valid
                  if (validators[validKey]) {
                    validHandle = validators[validKey]
                  }
                  if (_.isFunction(options.handle)) {
                    validHandle = options.handle
                  }
                  let arg = options.arg
                  if (_.isFunction(options.arg)) {
                    arg = options.arg.call(self)
                  }
                  let message = options.message
                  if (_.isFunction(options.message)) {
                    message = options.message.call(self)
                  }

                  const promise = new Promise(function (resolve, reject) {
                    try {
                      if (_.isFunction(validHandle.then)) {
                        validHandle(value, arg).then(function (validResult) {
                          change$ValidIng(validResult, validKey, message)
                          resolve({
                            valid: validResult,
                            message: message
                          })
                        })
                      } else {
                        change$ValidIng(validHandle(value, arg), validKey, message)
                        resolve({
                          valid: validHandle(value, arg),
                          message: message
                        })
                      }
                    } catch (e) {
                      reject(e)
                    }
                  })
                  validPromiseSet.push(promise)
                }

                return validPromiseSet
              }

              Promise.all(createPromiseSet($$validSet)).then(function () {
                changeAfter$Valid()
                resolve(self.valid$)
              }, function (e) {
                reject(e)
              })
            }
          })
        }
      }
    }
  },
  methods: {
    $setValidation ($$validation = this.$options.validation) {
      if (!_.isObject($$validation)) {
        return
      }
      const self = this
      self.$$validation = $$validation
      self.$$validationWatch = {}
      const setWatch = function (valid) {
        const self = this
        return self.$watch(valid, _.debounce(function (value, old) {
          if (value === old) {
            return
          }
          self.$validation.valid(valid)
        }, 150))
      }
      Object.keys($$validation).forEach(function (valid) {
        if (!self.$$validationWatch[valid]) {
          self.$$validationWatch[valid] = {}
        }
        if (!self.valid$[valid]) {
          self.$set(self.valid$, valid, Object.assign({$firstError: ''}, defaultValid))
        }
        const isWatch = _.find(self.$validation.getValidSet(valid), function (v) {
          return v.watch === false
        })
        if (isWatch) {
          return
        }
        if (self.$$validationWatch[valid]['$watch']) {
          self.$$validationWatch[valid]['$watch']()
        }
        self.$$validationWatch[valid]['$watch'] = setWatch.call(self, valid)
      })
    }
  }
}
const install = function (Vue, options) {
  Vue.mixin(validator)
}
export default {
  install,
  validator
}
<commit_msg>Update validator.js<commit_after>/**
 * Created by Lynn on 2017/6/26.
 * 验证器
 * 注册 vue mixin
 * 使用方式 , vue 组件中添加 validation 参数 格式
   validation: {
    'name': [
      {
        valid: 'required',//valid 可以是validators验证器中的字符串, 也可以自定义函数 , 支持promise
        message: '名称为必填'
      }
    ]
  }
 * 验证结果
 {
    '$init': true, // 原始
    '$valid': undefined, // 有效
    '$run': false, // 检查中
    '$error': {} // 错误列表
 }
 * 提供方法 this.$validation.valid 验证 , 可以指定 key
 * 提供方法 this.$validation.reset 重置验证
 */
import _ from 'lodash'
const validators = { // 结果真代表通过
  required: (val) => { // 必填
    let res = false
    if (Array.isArray(val)) {
      res = val.length > 0
    } else if (typeof val === 'number') {
      res = true
    } else if (typeof val === 'boolean') {
      res = val
    } else if (typeof val === 'string') {
      res = val.length > 0
    } else if (val !== null && typeof val === 'object') {
      res = Object
        .keys(val)
        .length > 0
    } else if (val === null || val === undefined) {
      res = false
    }
    return res
  },

  numeric: (val = '') => { // 数字
    const res = (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(val)
    return res
  },

  integer: (val = '') => { // 整数
    const res = (/^(-?[1-9]\d*|0)$/).test(val)
    return res
  },

  positive: (val = '') => { // 正整数
    const res = (/^\+?[1-9][0-9]*$/).test(val)
    return res
  },

  digits: (val = '') => { // 小数
    const res = (/^[\d() .:\-+#]+$/).test(val)
    return res
  },

  alpha: (val = '') => { // 字母
    const res = (/^[a-zA-Z]+$/).test(val)
    return res
  },

  alphaNum: (val = '') => { // 数字或者字母
    const res = (/^[a-zA-Z0-9]+$/).test(val)
    return res
  },

  email: (val = '') => { // email
    const res = (/^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i).test(val)
    return res
  },

  phone: (val = '') => { // 手机号
    const res = !!val.match(/^(0|86|17951)?(13|15|17|18|14)[0-9]{9}$/)
    return res
  },

  url: (val = '') => { // url地址
    const res = (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(val)
    return res
  },

  minLength: (val, arg) => { // 最大长度
    const res = val && val.length && val.length >= +arg
    return res
  },

  maxLength: (val = '', arg) => { // 最大长度
    const res = val.length <= +arg
    return res
  },

  minMaxLength: (val = '', arg) => { // 最大长度
    const max = val.length <= +arg.max
    const min = val.length >= +arg.min
    return min && max
  },

  length: (val, arg) => { // 长度
    const res = val && val.length === +arg
    return res
  },

  min: (val, arg) => { // 最小
    const res = val >= +arg
    return res
  },

  max: (val, arg) => { // 最大
    const res = val <= +arg
    return res
  },

  pattern: (val, arg) => { // 匹配规则
    const match = arg.match(new RegExp('^/(.*?)/([gimy]*)$'))
    const regex = new RegExp(match[1], match[2])
    const res = regex.test(val)
    return res
  },
  domain: (val) => {
    const regex = /^[a-z0-9A-Z\u4E00-\u9FA5][-a-zA-Z0-9\u4E00-\u9FA5]{0,62}(.[a-zA-Z0-9\u4E00-\u9FA5][-a-zA-Z0-9\u4E00-\u9FA5]{0,62})*(.[a-zA-Z\u4E00-\u9FA5]+)$/
    const res = regex.test(val)
    return res
  },
  ip: (val) => {
    const regex = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/
    const res = regex.test(val)
    return res
  },
  name: (val = '') => { // 中英文数字以及 - _ .
    const regex = new RegExp('^[A-Za-z0-9\u4e00-\u9fa5-_.]+$', 'g')
    const res = regex.test(val)
    return res
  }
}
const defaultValid = {
  '$init': true, // 原始
  '$valid': undefined, // 有效
  '$run': false, // 检查中
  '$error': {} // 错误列表
}
const setWatch = function (valid) {
  const self = this
  return self.$watch(valid, _.debounce(function (value, old) {
    if (value === old) {
      return
    }
    self
      .$validation
      .valid(valid)
  }, 150))
}
const validator = {
  created () {
    this.$setValidation()
  },
  data () {
    return {
      valid$: Object.assign({}, defaultValid)
    }
  },
  computed: {
    $validation () {
      const self = this
      if (!_.isObject(self.$$validation)) {
        return {}
      }
      return {
        getValidSet (key) {
          const $$validOptions = self.$$validation[key]
          let $$validSet = []
          if (!_.isArray($$validOptions) && !_.isObject($$validOptions)) {
            return $$validSet
          }
          if (_.isObject($$validOptions)) {
            $$validSet = [$$validOptions]
          }
          if (_.isArray($$validOptions)) {
            $$validSet = $$validOptions
          }
          return $$validSet
        },
        reset () {
          return new Promise(function (resolve, reject) {
            try {
              self.valid$ = Object.assign({}, defaultValid)
              self.$setValidation()
              self.$nextTick(() => {
                resolve.call(self)
              })
            } catch (e) {
              reject(e)
            }
          })
        },
        valid: (valid) => {
          return new Promise(function (resolve, reject) {
            if (!valid) {
              Object
                .keys(self.$$validation)
                .forEach(validOne)
            } else {
              validOne(valid)
            }
            function validOne (valid) {
              const value = _.get(self, valid, null)
              const $$validSet = self
                .$validation
                .getValidSet(valid)
              if ($$validSet.length <= 0) {
                return
              }
              function changeBefore$Valid () {
                self.valid$[valid]['$init'] = false
                self.valid$[valid]['$run'] = true
                self.valid$[valid]['$error'] = {}
                self.valid$['$init'] = false
                delete self.valid$['$error'][valid]
              }

              function change$ValidIng (validResult, validKey, message) {
                if (validResult === false) {
                  self.valid$[valid]['$error'][validKey] = message
                  self.valid$['$error'][valid] = self.valid$['$error'][valid] || {}
                  self.valid$['$error'][valid][validKey] = message
                  self.valid$[valid]['$firstError'] || (self.valid$[valid]['$firstError'] = message)
                }
                self.valid$[valid]['$run'] = false
              }

              function changeAfter$Valid () {
                let $validOne = Object
                  .keys(self.valid$[valid]['$error'])
                  .length <= 0
                let $valid = Object
                  .keys(self.valid$['$error'])
                  .length <= 0
                let $run = false
                Object
                  .keys(self.valid$)
                  .forEach(function (v) {
                    if (_.isObject(self.valid$[v]) && self.valid$[v]['$run'] === true) {
                      $run = true
                    }
                  })
                self.valid$[valid]['$valid'] = $validOne
                self.valid$['$valid'] = $valid
                self.valid$['$run'] = $run
              }

              function createPromiseSet (set) {
                changeBefore$Valid()
                const validPromiseSet = []
                set.forEach(optionsHandle)
                function optionsHandle (options) {
                  if (!_.isObject(options)) {
                    return
                  }
                  let validHandle = () => {}
                  const validKey = options.valid
                  if (validators[validKey]) {
                    validHandle = validators[validKey]
                  }
                  if (_.isFunction(options.handle)) {
                    validHandle = options.handle
                  }
                  let arg = options.arg
                  if (_.isFunction(options.arg)) {
                    arg = options
                      .arg
                      .call(self)
                  }
                  let message = options.message
                  if (_.isFunction(options.message)) {
                    message = options
                      .message
                      .call(self)
                  }

                  const promise = new Promise(function (resolve, reject) {
                    try {
                      if (_.isFunction(validHandle.then)) {
                        validHandle(value, arg)
                          .then(function (validResult) {
                            change$ValidIng(validResult, validKey, message)
                            resolve({valid: validResult, message: message})
                          })
                      } else {
                        change$ValidIng(validHandle(value, arg), validKey, message)
                        resolve({
                          valid: validHandle(value, arg),
                          message: message
                        })
                      }
                    } catch (e) {
                      reject(e)
                    }
                  })
                  validPromiseSet.push(promise)
                }

                return validPromiseSet
              }

              Promise
                .all(createPromiseSet($$validSet))
                .then(function () {
                  changeAfter$Valid()
                  resolve(self.valid$)
                }, function (e) {
                  reject(e)
                })
            }
          })
        }
      }
    }
  },
  methods: {
    $setValidation ($$validation = this.$options.validation) {
      if (!_.isObject($$validation)) {
        return
      }
      const self = this
      self.$$validation = $$validation
      self.$$validationWatch = {}
      Object
        .keys($$validation)
        .forEach(function (valid) {
          if (!self.$$validationWatch[valid]) {
            self.$$validationWatch[valid] = {}
          }
          if (!self.valid$[valid]) {
            self.$set(self.valid$, valid, Object.assign({
              $firstError: ''
            }, defaultValid))
          }
          const isWatch = _.find(self.$validation.getValidSet(valid), function (v) {
            return v.watch === false
          })
          if (isWatch) {
            return
          }
          if (self.$$validationWatch[valid]['$watch']) {
            self.$$validationWatch[valid]['$watch']()
          }
          self.$$validationWatch[valid]['$watch'] = setWatch.call(self, valid)
        })
    }
  }
}
const install = function (Vue, options) {
  Vue.mixin(validator)
}
export default {
  install,
  validator
}
<|endoftext|>