var app = angular.module('x', ['ui.codemirror']);
/* =XML_Autocomplete
* Taken directly from http://codemirror.net/demo/xmlcomplete.html
-----------------------------------------------------------------------------*/
app.factory('XML_Autocomplete', function(){
var dummy = {
attrs: {
color: ["red", "green", "blue", "purple", "white", "black", "yellow"],
size: ["large", "medium", "small"],
description: null
},
children: []
};
var tags = {
"!top": ["top"],
top: {
attrs: {
lang: ["en", "de", "fr", "nl"],
freeform: null
},
children: ["animal", "plant"]
},
animal: {
attrs: {
name: null,
isduck: ["yes", "no"]
},
children: ["wings", "feet", "body", "head", "tail"]
},
plant: {
attrs: {name: null},
children: ["leaves", "stem", "flowers"]
},
wings: dummy, feet: dummy, body: dummy, head: dummy, tail: dummy,
leaves: dummy, stem: dummy, flowers: dummy
};
function completeAfter(cm, pred) {
var cur = cm.getCursor();
if (!pred || pred()) setTimeout(function() {
if (!cm.state.completionActive)
CodeMirror.showHint(cm, CodeMirror.hint.xml, {schemaInfo: tags, completeSingle: false});
}, 100);
return CodeMirror.Pass;
}
function completeIfAfterLt(cm) {
return completeAfter(cm, function() {
var cur = cm.getCursor();
return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) == "<";
});
}
function completeIfInTag(cm) {
return completeAfter(cm, function() {
var tok = cm.getTokenAt(cm.getCursor());
if (tok.type == "string" && (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1)) return false;
var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state;
return inner.tagName;
});
}
return {
mode: "xml",
extraKeys: {
"'<'": completeAfter,
"'/'": completeIfAfterLt,
"' '": completeIfInTag,
"'='": completeIfInTag,
"Ctrl-Space": function(cm) {
CodeMirror.showHint(cm, CodeMirror.hint.xml, {schemaInfo: tags});
}
}
};
});
/* =CodemirrorCtrl
* Used for the demo
-----------------------------------------------------------------------------*/
app.controller('CodemirrorCtrl', ['$scope', 'XML_Autocomplete',function($scope, xmlAC) {
$scope.cmModel = '<!-- write some xml below -->';
$scope.cmOption = angular.extend(xmlAC, {
lineNumbers: true
});
}]);
<!DOCTYPE html>
<html ng-app="x">
<head>
<meta charset="utf-8" />
<title>UI.CodeMirror: XML Autocomplete demo</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<!-- Le css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css">
<!-- Le codemirror things... -->
<link rel="stylesheet" type="text/css" href="http://codemirror.net/lib/codemirror.css"/>
<link rel="stylesheet" type="text/css" href="http://codemirror.net/addon/hint/show-hint.css"/>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<div class="jumbotron">
<div class="container">
<h1>UI.CodeMirror: XML Autocomplete demo</h1>
</div>
</div>
<section ng-controller="CodemirrorCtrl">
<div class="container">
<div>
<textarea ui-codemirror="cmOption" ng-model="cmModel"></textarea>
</div>
<p>Press <strong>ctrl-space</strong>, or type a '<' character to
activate autocompletion. This demo defines a simple schema that
guides completion. The schema can be customized—see
the <a href="../doc/manual.html#addon_xml-hint">manual</a>.</p>
<p>Development of the <code>xml-hint</code> addon was kindly
sponsored
by <a href="http://www.xperiment.mobi">www.xperiment.mobi</a>.</p>
</div>
</section>
</body>
<!-- Le vendor... -->
<script src="http://code.jquery.com/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<!-- Le codemirror things... -->
<script src="http://codemirror.net/lib/codemirror.js"></script>
<script src="http://codemirror.net/addon/hint/show-hint.js"></script>
<script src="http://codemirror.net/addon/hint/xml-hint.js"></script>
<script src="http://codemirror.net/mode/xml/xml.js"></script>
<script src="ui-codemirror.js"></script>
<!-- Le custom js -->
<script src="app.js"></script>
</html>
/* Put your css in here */
.CodeMirror { border: 1px solid #eee; }
/* =TWBS HACK
-----------------------------------------------------------------------------*/
.jumbotron {
position: relative;
font-size: 16px;
color: #fff;
color: rgba(255,255,255,.75);
text-align: center;
background-color: #563d7c;
border-radius: 0;
}
'use strict';
/**
* Binds a CodeMirror widget to a <textarea> element.
*/
angular.module('ui.codemirror', []).constant('uiCodemirrorConfig', {}).directive('uiCodemirror', [
'uiCodemirrorConfig',
function (uiCodemirrorConfig) {
return {
restrict: 'EA',
require: '?ngModel',
priority: 1,
compile: function compile() {
// Require CodeMirror
if (angular.isUndefined(window.CodeMirror)) {
throw new Error('ui-codemirror need CodeMirror to work... (o rly?)');
}
return function postLink(scope, iElement, iAttrs, ngModel) {
var options, opts, codeMirror, initialTextValue;
initialTextValue = iElement.text();
options = uiCodemirrorConfig.codemirror || {};
opts = angular.extend({ value: initialTextValue }, options, scope.$eval(iAttrs.uiCodemirror), scope.$eval(iAttrs.uiCodemirrorOpts));
if (iElement[0].tagName === 'TEXTAREA') {
// Might bug but still ...
codeMirror = window.CodeMirror.fromTextArea(iElement[0], opts);
} else {
iElement.html('');
codeMirror = new window.CodeMirror(function (cm_el) {
iElement.append(cm_el);
}, opts);
}
if (iAttrs.uiCodemirror || iAttrs.uiCodemirrorOpts) {
var codemirrorDefaultsKeys = Object.keys(window.CodeMirror.defaults);
scope.$watch(iAttrs.uiCodemirror || iAttrs.uiCodemirrorOpts, function updateOptions(newValues, oldValue) {
if (!angular.isObject(newValues)) {
return;
}
codemirrorDefaultsKeys.forEach(function (key) {
if (newValues.hasOwnProperty(key)) {
if (oldValue && newValues[key] === oldValue[key]) {
return;
}
codeMirror.setOption(key, newValues[key]);
}
});
}, true);
}
if (ngModel) {
// CodeMirror expects a string, so make sure it gets one.
// This does not change the model.
ngModel.$formatters.push(function (value) {
if (angular.isUndefined(value) || value === null) {
return '';
} else if (angular.isObject(value) || angular.isArray(value)) {
throw new Error('ui-codemirror cannot use an object or an array as a model');
}
return value;
});
// Override the ngModelController $render method, which is what gets called when the model is updated.
// This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
ngModel.$render = function () {
//Code mirror expects a string so make sure it gets one
//Although the formatter have already done this, it can be possible that another formatter returns undefined (for example the required directive)
var safeViewValue = ngModel.$viewValue || '';
codeMirror.setValue(safeViewValue);
};
// Keep the ngModel in sync with changes from CodeMirror
codeMirror.on('change', function (instance) {
var newValue = instance.getValue();
if (newValue !== ngModel.$viewValue) {
// Changes to the model from a callback need to be wrapped in $apply or angular will not notice them
scope.$apply(function () {
ngModel.$setViewValue(newValue);
});
}
});
}
// Watch ui-refresh and refresh the directive
if (iAttrs.uiRefresh) {
scope.$watch(iAttrs.uiRefresh, function (newVal, oldVal) {
// Skip the initial watch firing
if (newVal !== oldVal) {
codeMirror.refresh();
}
});
}
// Allow access to the CodeMirror instance through a broadcasted event
// eg: $broadcast('CodeMirror', function(cm){...});
scope.$on('CodeMirror', function (event, callback) {
if (angular.isFunction(callback)) {
callback(codeMirror);
} else {
throw new Error('the CodeMirror event requires a callback function');
}
});
// onLoad callback
if (angular.isFunction(opts.onLoad)) {
opts.onLoad(codeMirror);
}
};
}
};
}
]);