mercredi 6 mai 2015

Tinymce editor becomes empty when ng-repeat list is reordered

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
        }
    };
}]);

Aucun commentaire :

Enregistrer un commentaire