mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
- parse jsdocs from source code - generate prerendered (markdown + mustache) partials - generate json - generate scenario runner for examples in docs - basic angular doc viewer
381 lines
No EOL
11 KiB
JavaScript
381 lines
No EOL
11 KiB
JavaScript
/**
|
|
*
|
|
*/
|
|
|
|
function modelAccessor(scope, element) {
|
|
var expr = element.attr('name');
|
|
if (!expr) throw "Required field 'name' not found.";
|
|
return {
|
|
get: function() {
|
|
return scope.$eval(expr);
|
|
},
|
|
set: function(value) {
|
|
if (value !== _undefined) {
|
|
return scope.$tryEval(expr + '=' + toJson(value), element);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function modelFormattedAccessor(scope, element) {
|
|
var accessor = modelAccessor(scope, element),
|
|
formatterName = element.attr('ng:format') || NOOP,
|
|
formatter = angularFormatter(formatterName);
|
|
if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
|
|
return {
|
|
get: function() {
|
|
return formatter.format(accessor.get());
|
|
},
|
|
set: function(value) {
|
|
return accessor.set(formatter.parse(value));
|
|
}
|
|
};
|
|
}
|
|
|
|
function compileValidator(expr) {
|
|
return parser(expr).validator()();
|
|
}
|
|
|
|
function valueAccessor(scope, element) {
|
|
var validatorName = element.attr('ng:validate') || NOOP,
|
|
validator = compileValidator(validatorName),
|
|
requiredExpr = element.attr('ng:required'),
|
|
formatterName = element.attr('ng:format') || NOOP,
|
|
formatter = angularFormatter(formatterName),
|
|
format, parse, lastError, required,
|
|
invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop};
|
|
if (!validator) throw "Validator named '" + validatorName + "' not found.";
|
|
if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
|
|
format = formatter.format;
|
|
parse = formatter.parse;
|
|
if (requiredExpr) {
|
|
scope.$watch(requiredExpr, function(newValue) {
|
|
required = newValue;
|
|
validate();
|
|
});
|
|
} else {
|
|
required = requiredExpr === '';
|
|
}
|
|
|
|
element.data('$validate', validate);
|
|
return {
|
|
get: function(){
|
|
if (lastError)
|
|
elementError(element, NG_VALIDATION_ERROR, _null);
|
|
try {
|
|
var value = parse(element.val());
|
|
validate();
|
|
return value;
|
|
} catch (e) {
|
|
lastError = e;
|
|
elementError(element, NG_VALIDATION_ERROR, e);
|
|
}
|
|
},
|
|
set: function(value) {
|
|
var oldValue = element.val(),
|
|
newValue = format(value);
|
|
if (oldValue != newValue) {
|
|
element.val(newValue || ''); // needed for ie
|
|
}
|
|
validate();
|
|
}
|
|
};
|
|
|
|
function validate() {
|
|
var value = trim(element.val());
|
|
if (element[0].disabled || element[0].readOnly) {
|
|
elementError(element, NG_VALIDATION_ERROR, _null);
|
|
invalidWidgets.markValid(element);
|
|
} else {
|
|
var error, validateScope = inherit(scope, {$element:element});
|
|
error = required && !value ?
|
|
'Required' :
|
|
(value ? validator(validateScope, value) : _null);
|
|
elementError(element, NG_VALIDATION_ERROR, error);
|
|
lastError = error;
|
|
if (error) {
|
|
invalidWidgets.markInvalid(element);
|
|
} else {
|
|
invalidWidgets.markValid(element);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkedAccessor(scope, element) {
|
|
var domElement = element[0], elementValue = domElement.value;
|
|
return {
|
|
get: function(){
|
|
return !!domElement.checked;
|
|
},
|
|
set: function(value){
|
|
domElement.checked = toBoolean(value);
|
|
}
|
|
};
|
|
}
|
|
|
|
function radioAccessor(scope, element) {
|
|
var domElement = element[0];
|
|
return {
|
|
get: function(){
|
|
return domElement.checked ? domElement.value : _null;
|
|
},
|
|
set: function(value){
|
|
domElement.checked = value == domElement.value;
|
|
}
|
|
};
|
|
}
|
|
|
|
function optionsAccessor(scope, element) {
|
|
var options = element[0].options;
|
|
return {
|
|
get: function(){
|
|
var values = [];
|
|
foreach(options, function(option){
|
|
if (option.selected) values.push(option.value);
|
|
});
|
|
return values;
|
|
},
|
|
set: function(values){
|
|
var keys = {};
|
|
foreach(values, function(value){ keys[value] = true; });
|
|
foreach(options, function(option){
|
|
option.selected = keys[option.value];
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
function noopAccessor() { return { get: noop, set: noop }; }
|
|
|
|
var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue()),
|
|
buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
|
|
INPUT_TYPE = {
|
|
'text': textWidget,
|
|
'textarea': textWidget,
|
|
'hidden': textWidget,
|
|
'password': textWidget,
|
|
'button': buttonWidget,
|
|
'submit': buttonWidget,
|
|
'reset': buttonWidget,
|
|
'image': buttonWidget,
|
|
'checkbox': inputWidget('click', modelFormattedAccessor, checkedAccessor, initWidgetValue(false)),
|
|
'radio': inputWidget('click', modelFormattedAccessor, radioAccessor, radioInit),
|
|
'select-one': inputWidget('change', modelFormattedAccessor, valueAccessor, initWidgetValue(_null)),
|
|
'select-multiple': inputWidget('change', modelFormattedAccessor, optionsAccessor, initWidgetValue([]))
|
|
// 'file': fileWidget???
|
|
};
|
|
|
|
function initWidgetValue(initValue) {
|
|
return function (model, view) {
|
|
var value = view.get();
|
|
if (!value && isDefined(initValue)) {
|
|
value = copy(initValue);
|
|
}
|
|
if (isUndefined(model.get()) && isDefined(value)) {
|
|
model.set(value);
|
|
}
|
|
};
|
|
}
|
|
|
|
function radioInit(model, view, element) {
|
|
var modelValue = model.get(), viewValue = view.get(), input = element[0];
|
|
input.checked = false;
|
|
input.name = this.$id + '@' + input.name;
|
|
if (isUndefined(modelValue)) {
|
|
model.set(modelValue = _null);
|
|
}
|
|
if (modelValue == _null && viewValue !== _null) {
|
|
model.set(viewValue);
|
|
}
|
|
view.set(modelValue);
|
|
}
|
|
|
|
function inputWidget(events, modelAccessor, viewAccessor, initFn) {
|
|
return function(element) {
|
|
var scope = this,
|
|
model = modelAccessor(scope, element),
|
|
view = viewAccessor(scope, element),
|
|
action = element.attr('ng:change') || '',
|
|
lastValue;
|
|
initFn.call(scope, model, view, element);
|
|
this.$eval(element.attr('ng:init')||'');
|
|
// Don't register a handler if we are a button (noopAccessor) and there is no action
|
|
if (action || modelAccessor !== noopAccessor) {
|
|
element.bind(events, function(event){
|
|
model.set(view.get());
|
|
lastValue = model.get();
|
|
scope.$tryEval(action, element);
|
|
scope.$root.$eval();
|
|
});
|
|
}
|
|
function updateView(){
|
|
view.set(lastValue = model.get());
|
|
}
|
|
updateView();
|
|
element.data('$update', updateView);
|
|
scope.$watch(model.get, function(value){
|
|
if (lastValue !== value) {
|
|
view.set(lastValue = value);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
function inputWidgetSelector(element){
|
|
this.directives(true);
|
|
return INPUT_TYPE[lowercase(element[0].type)] || noop;
|
|
}
|
|
|
|
angularWidget('input', inputWidgetSelector);
|
|
angularWidget('textarea', inputWidgetSelector);
|
|
angularWidget('button', inputWidgetSelector);
|
|
angularWidget('select', function(element){
|
|
this.descend(true);
|
|
return inputWidgetSelector.call(this, element);
|
|
});
|
|
|
|
angularWidget('option', function(){
|
|
this.descend(true);
|
|
this.directives(true);
|
|
return function(element) {
|
|
this.$postEval(element.parent().data('$update'));
|
|
};
|
|
});
|
|
|
|
|
|
/*ng:doc
|
|
* @type widget
|
|
* @name ng:include
|
|
*
|
|
* @description
|
|
*
|
|
* @example
|
|
*
|
|
* @scenario
|
|
*/
|
|
angularWidget('ng:include', function(element){
|
|
var compiler = this,
|
|
srcExp = element.attr("src"),
|
|
scopeExp = element.attr("scope") || '';
|
|
if (element[0]['ng:compiled']) {
|
|
this.descend(true);
|
|
this.directives(true);
|
|
} else {
|
|
element[0]['ng:compiled'] = true;
|
|
return extend(function(xhr, element){
|
|
var scope = this, childScope;
|
|
var changeCounter = 0;
|
|
var preventRecursion = false;
|
|
function incrementChange(){ changeCounter++;}
|
|
this.$watch(srcExp, incrementChange);
|
|
this.$watch(scopeExp, incrementChange);
|
|
scope.$onEval(function(){
|
|
if (childScope && !preventRecursion) {
|
|
preventRecursion = true;
|
|
try {
|
|
childScope.$eval();
|
|
} finally {
|
|
preventRecursion = false;
|
|
}
|
|
}
|
|
});
|
|
this.$watch(function(){return changeCounter;}, function(){
|
|
var src = this.$eval(srcExp),
|
|
useScope = this.$eval(scopeExp);
|
|
if (src) {
|
|
xhr('GET', src, function(code, response){
|
|
element.html(response);
|
|
childScope = useScope || createScope(scope);
|
|
compiler.compile(element)(element, childScope);
|
|
childScope.$init();
|
|
});
|
|
} else {
|
|
childScope = null;
|
|
element.html('');
|
|
}
|
|
});
|
|
}, {$inject:['$xhr.cache']});
|
|
}
|
|
});
|
|
|
|
var ngSwitch = angularWidget('ng:switch', function (element){
|
|
var compiler = this,
|
|
watchExpr = element.attr("on"),
|
|
usingExpr = (element.attr("using") || 'equals'),
|
|
usingExprParams = usingExpr.split(":"),
|
|
usingFn = ngSwitch[usingExprParams.shift()],
|
|
changeExpr = element.attr('change') || '',
|
|
cases = [];
|
|
if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
|
|
eachNode(element, function(caseElement){
|
|
var when = caseElement.attr('ng:switch-when');
|
|
if (when) {
|
|
cases.push({
|
|
when: function(scope, value){
|
|
var args = [value, when];
|
|
foreach(usingExprParams, function(arg){
|
|
args.push(arg);
|
|
});
|
|
return usingFn.apply(scope, args);
|
|
},
|
|
change: changeExpr,
|
|
element: caseElement,
|
|
template: compiler.compile(caseElement)
|
|
});
|
|
}
|
|
});
|
|
|
|
// this needs to be here for IE
|
|
foreach(cases, function(_case){
|
|
_case.element.remove();
|
|
});
|
|
|
|
element.html('');
|
|
return function(element){
|
|
var scope = this, childScope;
|
|
this.$watch(watchExpr, function(value){
|
|
element.html('');
|
|
childScope = createScope(scope);
|
|
foreach(cases, function(switchCase){
|
|
if (switchCase.when(childScope, value)) {
|
|
var caseElement = quickClone(switchCase.element);
|
|
element.append(caseElement);
|
|
childScope.$tryEval(switchCase.change, element);
|
|
switchCase.template(caseElement, childScope);
|
|
childScope.$init();
|
|
}
|
|
});
|
|
});
|
|
scope.$onEval(function(){
|
|
if (childScope) childScope.$eval();
|
|
});
|
|
};
|
|
}, {
|
|
equals: function(on, when) {
|
|
return on == when;
|
|
},
|
|
route: switchRouteMatcher
|
|
});
|
|
|
|
|
|
/*
|
|
* Modifies the default behavior of html A tag, so that the default action is prevented when href
|
|
* attribute is empty.
|
|
*
|
|
* The reasoning for this change is to allow easy creation of action links with ng:click without
|
|
* changing the location or causing page reloads, e.g.:
|
|
* <a href="" ng:click="model.$save()">Save</a>
|
|
*/
|
|
angular.widget('a', function() {
|
|
this.descend(true);
|
|
this.directives(true);
|
|
|
|
return function(element) {
|
|
if (element.attr('href') === '') {
|
|
element.bind('click', function(event){
|
|
event.preventDefault();
|
|
});
|
|
}
|
|
};
|
|
}); |