/**
* Accessing validation information in your templates is really simple but the pathing can be quite long. For example, if we want to display the error `message` for the `username` attribute, it would look something like this:
*
* ```handlebars
* {{model.validations.attrs.username.message}}
* ```
*
* ## The V-Get Helper
* To bypass such long pathing, you can use the `v-get` helper.
*
* _**Notice**: Ember v1.13.0 is not supported due to a bug. Please use Ember v1.13.1 and higher or Ember v1.12.* and lower_
*
* **Access global model properties**
*
* ```handlebars
* {{v-get model 'isValid'}}
* ```
*
* **Access attribute specific properties**
*
* ```handlebars
* {{v-get model 'username' 'message'}}
* ```
*
* **Access model relationship validations**
*
* Say we have a `user` model with a `details` attribute that is a belongsTo relationship, to access validations on the `details` attribute/model we can access it as such.
*
* ```handlebars
* {{v-get model.details 'isValid'}}
* {{v-get model.details 'firstName' 'message'}}
* ```
*
* What's awesome about this is that you can pass in bound properties!
*
* ```handlebars
* {{v-get model attr prop}}
* {{v-get model prop}}
* ```
*
* Here is a more extensive example:
* ```handlebars
* <form>
* {{input value=model.username placeholder="Username"}}
* {{#if (v-get model 'username' 'isInvalid')}}
* <div class="error">
* {{v-get model 'username' 'message'}}
* </div>
* {{/if}}
*
* <button type="submit" disabled={{v-get model 'isInvalid'}}>Submit</button>
* </form>
* ```
*
* @module Templating
* @main Templating
*/
function VGet(options) {
this.options = options;
this.syntax = null; // set by HTMLBars
}
VGet.prototype.transform = function(ast) {
var context = this;
var walker = new this.syntax.Walker();
walker.visit(ast, function(node) {
if (context.validate(node)) {
context.processNode(node);
}
});
return ast;
};
VGet.prototype.validate = function(node) {
return ['BlockStatement', 'MustacheStatement', 'ElementNode'].indexOf(node.type) > -1;
};
VGet.prototype.processNode = function(node) {
var type = node.type;
node = unwrapNode(node);
// {{v-get model 'username' 'isValid'}}
if (type === 'MustacheStatement' && node.path.original === 'v-get') {
this.transformToGet(node);
}
this.processNodeParams(node);
this.processNodeHash(node);
this.processNodeAttributes(node);
};
/**
* {{#if (v-get model 'username' 'isValid')}} {{/if}}
* @param {AST.Node} node
*/
VGet.prototype.processNodeParams = function(node) {
if (node.params) {
for (var i = 0; i < node.params.length; i++) {
var param = node.params[i];
if (param.type === 'SubExpression') {
if (param.path.original === 'v-get') {
this.transformToGet(param);
} else {
this.processNode(param);
}
}
}
}
};
/**
* {{x-component prop=(v-get model 'isValid')}}
* @param {AST.Node} node
*/
VGet.prototype.processNodeHash = function(node) {
if (node.hash && node.hash.pairs) {
for (var i = 0; i < node.hash.pairs.length; i++) {
var pair = node.hash.pairs[i];
if (pair.value.type === 'SubExpression') {
if (pair.value.path.original === 'v-get') {
this.transformToGet(pair.value);
} else {
this.processNode(pair.value);
}
}
}
}
};
/**
* <button type="submit" disabled={{v-get model 'isInvalid'}}>Submit</button> (node.attributes)
* <div class="form-group {{if (v-get model 'isInvalid') 'has-error'}}">
* @param {AST.Node} node
*/
VGet.prototype.processNodeAttributes = function(node) {
var i;
if (node.attributes) {
for (i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
this.processNode(attr.value);
}
}
if (node.parts) {
for (i = 0; i < node.parts.length; i++) {
this.processNode(node.parts[i]);
}
}
};
/**
* Transform (v-get model 'username' 'isValid') to (get (get model.validations.attrs 'username') 'isValid') OR
* (v-get model 'isValid') to (get model.validations 'isValid')
* @param {AST.Node} node
* @return {AST.Node}
*/
VGet.prototype.transformToGet = function(node) {
node = unwrapNode(node);
var params = node.params;
var i = 0;
if (params.length < 2) {
throw new Error('{{v-get}} requires at least two arguments');
}
if (params[0].type !== 'PathExpression') {
throw new Error('The first argument to {{v-get}} must be a stream');
}
var root = this.syntax.builders.path(params[i++].original + '.validations');
if (params.length === 3) {
root = this.syntax.builders.path(root.original + '.attrs');
root = this.syntax.builders.sexpr(this.syntax.builders.path('get'), [root, params[i++]]); // (get model.validations.attrs 'username')
}
node.path = this.syntax.builders.path('get');
node.params = [root, params[i]];
};
// For compatibility with pre- and post-glimmer
function unwrapNode(node) {
if (node.sexpr) {
return node.sexpr;
} else {
return node;
}
}
module.exports = VGet;