github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/compiler/compile-props.js (about)

     1  import config from '../config'
     2  import { parseDirective } from '../parsers/directive'
     3  import { isSimplePath } from '../parsers/expression'
     4  import { defineReactive, withoutConversion } from '../observer/index'
     5  import propDef from '../directives/internal/prop'
     6  import {
     7    warn,
     8    camelize,
     9    hyphenate,
    10    getAttr,
    11    getBindAttr,
    12    isLiteral,
    13    toBoolean,
    14    toNumber,
    15    stripQuotes,
    16    isArray,
    17    isPlainObject,
    18    isObject,
    19    hasOwn
    20  } from '../util/index'
    21  
    22  const propBindingModes = config._propBindingModes
    23  const empty = {}
    24  
    25  // regexes
    26  const identRE = /^[$_a-zA-Z]+[\w$]*$/
    27  const settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
    28  
    29  /**
    30   * Compile props on a root element and return
    31   * a props link function.
    32   *
    33   * @param {Element|DocumentFragment} el
    34   * @param {Array} propOptions
    35   * @param {Vue} vm
    36   * @return {Function} propsLinkFn
    37   */
    38  
    39  export function compileProps (el, propOptions, vm) {
    40    var props = []
    41    var names = Object.keys(propOptions)
    42    var i = names.length
    43    var options, name, attr, value, path, parsed, prop
    44    while (i--) {
    45      name = names[i]
    46      options = propOptions[name] || empty
    47  
    48      if (process.env.NODE_ENV !== 'production' && name === '$data') {
    49        warn('Do not use $data as prop.', vm)
    50        continue
    51      }
    52  
    53      // props could contain dashes, which will be
    54      // interpreted as minus calculations by the parser
    55      // so we need to camelize the path here
    56      path = camelize(name)
    57      if (!identRE.test(path)) {
    58        process.env.NODE_ENV !== 'production' && warn(
    59          'Invalid prop key: "' + name + '". Prop keys ' +
    60          'must be valid identifiers.',
    61          vm
    62        )
    63        continue
    64      }
    65  
    66      prop = {
    67        name: name,
    68        path: path,
    69        options: options,
    70        mode: propBindingModes.ONE_WAY,
    71        raw: null
    72      }
    73  
    74      attr = hyphenate(name)
    75      // first check dynamic version
    76      if ((value = getBindAttr(el, attr)) === null) {
    77        if ((value = getBindAttr(el, attr + '.sync')) !== null) {
    78          prop.mode = propBindingModes.TWO_WAY
    79        } else if ((value = getBindAttr(el, attr + '.once')) !== null) {
    80          prop.mode = propBindingModes.ONE_TIME
    81        }
    82      }
    83      if (value !== null) {
    84        // has dynamic binding!
    85        prop.raw = value
    86        parsed = parseDirective(value)
    87        value = parsed.expression
    88        prop.filters = parsed.filters
    89        // check binding type
    90        if (isLiteral(value) && !parsed.filters) {
    91          // for expressions containing literal numbers and
    92          // booleans, there's no need to setup a prop binding,
    93          // so we can optimize them as a one-time set.
    94          prop.optimizedLiteral = true
    95        } else {
    96          prop.dynamic = true
    97          // check non-settable path for two-way bindings
    98          if (process.env.NODE_ENV !== 'production' &&
    99              prop.mode === propBindingModes.TWO_WAY &&
   100              !settablePathRE.test(value)) {
   101            prop.mode = propBindingModes.ONE_WAY
   102            warn(
   103              'Cannot bind two-way prop with non-settable ' +
   104              'parent path: ' + value,
   105              vm
   106            )
   107          }
   108        }
   109        prop.parentPath = value
   110  
   111        // warn required two-way
   112        if (
   113          process.env.NODE_ENV !== 'production' &&
   114          options.twoWay &&
   115          prop.mode !== propBindingModes.TWO_WAY
   116        ) {
   117          warn(
   118            'Prop "' + name + '" expects a two-way binding type.',
   119            vm
   120          )
   121        }
   122      } else if ((value = getAttr(el, attr)) !== null) {
   123        // has literal binding!
   124        prop.raw = value
   125      } else if (process.env.NODE_ENV !== 'production') {
   126        // check possible camelCase prop usage
   127        var lowerCaseName = path.toLowerCase()
   128        value = /[A-Z\-]/.test(name) && (
   129          el.getAttribute(lowerCaseName) ||
   130          el.getAttribute(':' + lowerCaseName) ||
   131          el.getAttribute('v-bind:' + lowerCaseName) ||
   132          el.getAttribute(':' + lowerCaseName + '.once') ||
   133          el.getAttribute('v-bind:' + lowerCaseName + '.once') ||
   134          el.getAttribute(':' + lowerCaseName + '.sync') ||
   135          el.getAttribute('v-bind:' + lowerCaseName + '.sync')
   136        )
   137        if (value) {
   138          warn(
   139            'Possible usage error for prop `' + lowerCaseName + '` - ' +
   140            'did you mean `' + attr + '`? HTML is case-insensitive, remember to use ' +
   141            'kebab-case for props in templates.',
   142            vm
   143          )
   144        } else if (options.required) {
   145          // warn missing required
   146          warn('Missing required prop: ' + name, vm)
   147        }
   148      }
   149      // push prop
   150      props.push(prop)
   151    }
   152    return makePropsLinkFn(props)
   153  }
   154  
   155  /**
   156   * Build a function that applies props to a vm.
   157   *
   158   * @param {Array} props
   159   * @return {Function} propsLinkFn
   160   */
   161  
   162  function makePropsLinkFn (props) {
   163    return function propsLinkFn (vm, scope) {
   164      // store resolved props info
   165      vm._props = {}
   166      var inlineProps = vm.$options.propsData
   167      var i = props.length
   168      var prop, path, options, value, raw
   169      while (i--) {
   170        prop = props[i]
   171        raw = prop.raw
   172        path = prop.path
   173        options = prop.options
   174        vm._props[path] = prop
   175        if (inlineProps && hasOwn(inlineProps, path)) {
   176          initProp(vm, prop, inlineProps[path])
   177        } if (raw === null) {
   178          // initialize absent prop
   179          initProp(vm, prop, undefined)
   180        } else if (prop.dynamic) {
   181          // dynamic prop
   182          if (prop.mode === propBindingModes.ONE_TIME) {
   183            // one time binding
   184            value = (scope || vm._context || vm).$get(prop.parentPath)
   185            initProp(vm, prop, value)
   186          } else {
   187            if (vm._context) {
   188              // dynamic binding
   189              vm._bindDir({
   190                name: 'prop',
   191                def: propDef,
   192                prop: prop
   193              }, null, null, scope) // el, host, scope
   194            } else {
   195              // root instance
   196              initProp(vm, prop, vm.$get(prop.parentPath))
   197            }
   198          }
   199        } else if (prop.optimizedLiteral) {
   200          // optimized literal, cast it and just set once
   201          var stripped = stripQuotes(raw)
   202          value = stripped === raw
   203            ? toBoolean(toNumber(raw))
   204            : stripped
   205          initProp(vm, prop, value)
   206        } else {
   207          // string literal, but we need to cater for
   208          // Boolean props with no value, or with same
   209          // literal value (e.g. disabled="disabled")
   210          // see https://github.com/vuejs/vue-loader/issues/182
   211          value = (
   212            options.type === Boolean &&
   213            (raw === '' || raw === hyphenate(prop.name))
   214          ) ? true
   215            : raw
   216          initProp(vm, prop, value)
   217        }
   218      }
   219    }
   220  }
   221  
   222  /**
   223   * Process a prop with a rawValue, applying necessary coersions,
   224   * default values & assertions and call the given callback with
   225   * processed value.
   226   *
   227   * @param {Vue} vm
   228   * @param {Object} prop
   229   * @param {*} rawValue
   230   * @param {Function} fn
   231   */
   232  
   233  function processPropValue (vm, prop, rawValue, fn) {
   234    const isSimple = prop.dynamic && isSimplePath(prop.parentPath)
   235    let value = rawValue
   236    if (value === undefined) {
   237      value = getPropDefaultValue(vm, prop)
   238    }
   239    value = coerceProp(prop, value)
   240    const coerced = value !== rawValue
   241    if (!assertProp(prop, value, vm)) {
   242      value = undefined
   243    }
   244    if (isSimple && !coerced) {
   245      withoutConversion(() => {
   246        fn(value)
   247      })
   248    } else {
   249      fn(value)
   250    }
   251  }
   252  
   253  /**
   254   * Set a prop's initial value on a vm and its data object.
   255   *
   256   * @param {Vue} vm
   257   * @param {Object} prop
   258   * @param {*} value
   259   */
   260  
   261  export function initProp (vm, prop, value) {
   262    processPropValue(vm, prop, value, value => {
   263      defineReactive(vm, prop.path, value)
   264    })
   265  }
   266  
   267  /**
   268   * Update a prop's value on a vm.
   269   *
   270   * @param {Vue} vm
   271   * @param {Object} prop
   272   * @param {*} value
   273   */
   274  
   275  export function updateProp (vm, prop, value) {
   276    processPropValue(vm, prop, value, value => {
   277      vm[prop.path] = value
   278    })
   279  }
   280  
   281  /**
   282   * Get the default value of a prop.
   283   *
   284   * @param {Vue} vm
   285   * @param {Object} prop
   286   * @return {*}
   287   */
   288  
   289  function getPropDefaultValue (vm, prop) {
   290    // no default, return undefined
   291    const options = prop.options
   292    if (!hasOwn(options, 'default')) {
   293      // absent boolean value defaults to false
   294      return options.type === Boolean
   295        ? false
   296        : undefined
   297    }
   298    var def = options.default
   299    // warn against non-factory defaults for Object & Array
   300    if (isObject(def)) {
   301      process.env.NODE_ENV !== 'production' && warn(
   302        'Invalid default value for prop "' + prop.name + '": ' +
   303        'Props with type Object/Array must use a factory function ' +
   304        'to return the default value.',
   305        vm
   306      )
   307    }
   308    // call factory function for non-Function types
   309    return typeof def === 'function' && options.type !== Function
   310      ? def.call(vm)
   311      : def
   312  }
   313  
   314  /**
   315   * Assert whether a prop is valid.
   316   *
   317   * @param {Object} prop
   318   * @param {*} value
   319   * @param {Vue} vm
   320   */
   321  
   322  function assertProp (prop, value, vm) {
   323    if (
   324      !prop.options.required && ( // non-required
   325        prop.raw === null ||      // abscent
   326        value == null             // null or undefined
   327      )
   328    ) {
   329      return true
   330    }
   331    var options = prop.options
   332    var type = options.type
   333    var valid = !type
   334    var expectedTypes = []
   335    if (type) {
   336      if (!isArray(type)) {
   337        type = [type]
   338      }
   339      for (var i = 0; i < type.length && !valid; i++) {
   340        var assertedType = assertType(value, type[i])
   341        expectedTypes.push(assertedType.expectedType)
   342        valid = assertedType.valid
   343      }
   344    }
   345    if (!valid) {
   346      if (process.env.NODE_ENV !== 'production') {
   347        warn(
   348          'Invalid prop: type check failed for prop "' + prop.name + '".' +
   349          ' Expected ' + expectedTypes.map(formatType).join(', ') +
   350          ', got ' + formatValue(value) + '.',
   351          vm
   352        )
   353      }
   354      return false
   355    }
   356    var validator = options.validator
   357    if (validator) {
   358      if (!validator(value)) {
   359        process.env.NODE_ENV !== 'production' && warn(
   360          'Invalid prop: custom validator check failed for prop "' + prop.name + '".',
   361          vm
   362        )
   363        return false
   364      }
   365    }
   366    return true
   367  }
   368  
   369  /**
   370   * Force parsing value with coerce option.
   371   *
   372   * @param {*} value
   373   * @param {Object} options
   374   * @return {*}
   375   */
   376  
   377  function coerceProp (prop, value) {
   378    var coerce = prop.options.coerce
   379    if (!coerce) {
   380      return value
   381    }
   382    // coerce is a function
   383    return coerce(value)
   384  }
   385  
   386  /**
   387   * Assert the type of a value
   388   *
   389   * @param {*} value
   390   * @param {Function} type
   391   * @return {Object}
   392   */
   393  
   394  function assertType (value, type) {
   395    var valid
   396    var expectedType
   397    if (type === String) {
   398      expectedType = 'string'
   399      valid = typeof value === expectedType
   400    } else if (type === Number) {
   401      expectedType = 'number'
   402      valid = typeof value === expectedType
   403    } else if (type === Boolean) {
   404      expectedType = 'boolean'
   405      valid = typeof value === expectedType
   406    } else if (type === Function) {
   407      expectedType = 'function'
   408      valid = typeof value === expectedType
   409    } else if (type === Object) {
   410      expectedType = 'object'
   411      valid = isPlainObject(value)
   412    } else if (type === Array) {
   413      expectedType = 'array'
   414      valid = isArray(value)
   415    } else {
   416      valid = value instanceof type
   417    }
   418    return {
   419      valid,
   420      expectedType
   421    }
   422  }
   423  
   424  /**
   425   * Format type for output
   426   *
   427   * @param {String} type
   428   * @return {String}
   429   */
   430  
   431  function formatType (type) {
   432    return type
   433      ? type.charAt(0).toUpperCase() + type.slice(1)
   434      : 'custom type'
   435  }
   436  
   437  /**
   438   * Format value
   439   *
   440   * @param {*} value
   441   * @return {String}
   442   */
   443  
   444  function formatValue (val) {
   445    return Object.prototype.toString.call(val).slice(8, -1)
   446  }