This is a simplified example of a bug we faced in our project recently.
I have a list of objects with "name" and "position" properties and wanna use TinyMCE editor instead of textarea to display "name".
Besides, the list is ordered by "position" property which is editable.
Noticed that once "position" property is changed(list is reordered), TinyMCE editor becomes empty.
Anyone has any ideas why it happens and how to fix this?
Example code: JsFiddle
HTML
<script src="http://ift.tt/1DTrZWe"></script>
<script src="//tinymce.cachefly.net/4.1/tinymce.min.js"></script>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<p>List of activities:</p>
<div ng-repeat="activity in model.activities | orderBy: 'position'">
<label for="$index">Position</label>
<input id="$index" type="number" ng-model="activity.position" style="width: 50px">
<textarea ng-model="activity.name" rows="2" cols="10"></textarea>
<hr>
</div>
</div>
</body>
JS
var myApp = angular.module('myApp', ['ui.tinymce']);
/**
* Binds a TinyMCE widget to <textarea> elements.
*/
angular.module('ui.tinymce', [])
.value('uiTinymceConfig', {})
.directive('uiTinymce', ['uiTinymceConfig', function (uiTinymceConfig) {
uiTinymceConfig = uiTinymceConfig || {};
var generatedIds = 0;
return {
priority: 10,
require: 'ngModel',
link: function (scope, elm, attrs, ngModel) {
var expression, options, tinyInstance,
updateView = function () {
ngModel.$setViewValue(elm.val());
if (!scope.$root.$$phase) {
scope.$apply();
}
};
// generate an ID if not present
if (!attrs.id) {
attrs.$set('id', 'uiTinymce' + generatedIds++);
}
if (attrs.uiTinymce) {
expression = scope.$eval(attrs.uiTinymce);
} else {
expression = {};
}
// make config'ed setup method available
if (expression.setup) {
var configSetup = expression.setup;
delete expression.setup;
}
options = {
// Update model when calling setContent (such as from the source editor popup)
setup: function (ed) {
var args;
ed.on('init', function(args) {
ngModel.$render();
ngModel.$setPristine();
});
// Update model on button click
ed.on('ExecCommand', function (e) {
ed.save();
updateView();
});
// Update model on keypress
ed.on('KeyUp', function (e) {
ed.save();
updateView();
});
// Update model on change, i.e. copy/pasted text, plugins altering content
ed.on('SetContent', function (e) {
if (!e.initial && ngModel.$viewValue !== e.content) {
ed.save();
updateView();
}
});
ed.on('blur', function(e) {
elm.blur();
});
// Update model when an object has been resized (table, image)
ed.on('ObjectResized', function (e) {
ed.save();
updateView();
});
if (configSetup) {
configSetup(ed);
}
},
mode: 'exact',
elements: attrs.id
};
// extend options with initial uiTinymceConfig and options from directive attribute value
angular.extend(options, uiTinymceConfig, expression);
setTimeout(function () {
tinymce.init(options);
});
ngModel.$render = function() {
if (!tinyInstance) {
tinyInstance = tinymce.get(attrs.id);
}
if (tinyInstance) {
tinyInstance.setContent(ngModel.$viewValue || '');
}
};
scope.$on('$destroy', function() {
if (!tinyInstance) { tinyInstance = tinymce.get(attrs.id); }
if (tinyInstance) {
tinyInstance.remove();
tinyInstance = null;
}
});
}
};
}]);
myApp.controller("MyCtrl", ["$scope", function($scope) {
$scope.model = {
activities: [
{name: "activity 1", position: 1},
{name: "activity 2", position: 2},
{name: "activity 3", position: 3},
{name: "activity 4", position: 4},
{name: "activity 5", position: 5}
]
};
$scope.tinyMceOptions = {
selector: "textarea",
theme: "modern",
plugins: [
"autolink lists link charmap print preview hr anchor pagebreak autoresize",//advlist
"searchreplace visualblocks visualchars code",
"insertdatetime nonbreaking save table directionality",
"emoticons template paste textcolor colorpicker textpattern"
],
toolbar1: "bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | forecolor backcolor | undo redo | link | pastetext",
paste_auto_cleanup_on_paste: true,
paste_strip_class_attributes: 'mso',
paste_data_images: false,
theme_advanced_buttons3_add: "pastetext,pasteword,selectall",
image_advtab: true,
//templates: [
// {title: 'Test template 1', content: 'Test 1'},
// {title: 'Test template 2', content: 'Test 2'}
//],
browser_spellcheck: true,
menubar: false,
//theme_advanced_disable: "bullist,numlist",
target_list: [{ title: 'New page', value: '_blank' }],
//advlist_number_styles: [
// {title : 'Standard', styles : {listStyleType : ''}},
// {title : 'a. b. c.', styles : {listStyleType : 'lower-alpha'}}
//],
//spellchecker_languages: "+English=en",
//spellchecker_rpc_url: 'spellchecker.php',
handle_event_callback: function (e) {
// put logic here for keypress
}
};
}]);