JSFiddle + TinyMCE + AngularJS - angularjs

I'm experiencing an issue trying to use tinymce API inside an angular directive in JSFiddle.
Here is the example
The tinymce editor is initialised just fine, there's no errors in browser console. But I get 'undefined' if I try to get an instance of the tinymce Editor.
The question is: why does tinymce.get(id); result in undefined?
HTML:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<my-editor ng-model="text"></my-editor>
</div>
</div>
JS:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
});
app.directive('myEditor', function () {
var uniqueId = 0;
return {
restrict: 'E',
require: 'ngModel',
scope: true,
template: '<textarea></textarea>',
link: function (scope, element, attrs, ngModel) {
var id = 'myEditor_' + uniqueId++;
element.find('textarea').attr('id', id);
tinymce.init({
selector: '#' + id
});
var editor = tinymce.get(id);
alert(editor); **// why is this undefined?**
}
}
});
I've also played with options in Frameworks & Extensions section of JSFiddle. But with no success.

You are dealing with the issue, where the elements have not been appended to the dom when you are doing your alert. (look at the html in firebug)
<my-editor ng-model="text" class="ng-scope ng-pristine ng-valid">
<textarea id="myEditor_0"></textarea>
</my-editor>
Placing the alert inside of a setTimeout will allow you to alert() the object.
setTimeout(function(){
var editor = tinymce.get(id);
alert(editor); // why is this undefined?
},0);

The proper way to go is to set the init_instance_callback option in tinyMCE.init or in tinymceOptions if you are using angular-ui-tinymce :
$scope.tinymceOptions = {
init_instance_callback : function(editor) {
MyCtrl.editor=editor
console.log("Editor: " + editor.id + " is now initialized.");
editor.on('Change', function(editor, e) {
MyCtrl.func()
});
}

Additionally to the answer Mark gave:
You will need to wait for the editor to get initalized and ready to be usable.
For this you may use the onInit tinymce handler. onInit gets fired when the editor is ready.

Related

Angularjs- Disable button until image is rendered [duplicate]

I've been searching for an answer to simple but not trivial question: What is a right way to catch image' onload event in Angular only with jqLite? I found this question , but I want some solution with directives.
So as I said, this is not accepted for me:
.controller("MyCtrl", function($scope){
// ...
img.onload = function () {
// ...
}
because it is in controller, not in directive.
Here's a re-usable directive in the style of angular's inbuilt event handling directives:
angular.module('sbLoad', [])
.directive('sbLoad', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var fn = $parse(attrs.sbLoad);
elem.on('load', function (event) {
scope.$apply(function() {
fn(scope, { $event: event });
});
});
}
};
}]);
When the img load event is fired the expression in the sb-load attribute is evaluated in the current scope along with the load event, passed in as $event. Here's how to use it:
HTML
<div ng-controller="MyCtrl">
<img sb-load="onImgLoad($event)">
</div>
JS
.controller("MyCtrl", function($scope){
// ...
$scope.onImgLoad = function (event) {
// ...
}
Note: "sb" is just the prefix I use for my custom directives.
Ok, jqLite' bind method doing well its job. It goes like this:
We are adding directive' name as attribute in our img tag . In my case , after loading and depending on its dimensions , image have to change its class name from "horizontal" to "vertical" , so directive's name will be "orientable" :
<img ng-src="image_path.jpg" class="horizontal" orientable />
And then we are creating simple directive like this:
var app = angular.module('myApp',[]);
app.directive('orientable', function () {
return {
link: function(scope, element, attrs) {
element.bind("load" , function(e){
// success, "onload" catched
// now we can do specific stuff:
if(this.naturalHeight > this.naturalWidth){
this.className = "vertical";
}
});
}
}
});
Example (explicit graphics!): http://jsfiddle.net/5nZYZ/63/
AngularJS V1.7.3 Added the ng-on-xxx directive:
<div ng-controller="MyCtrl">
<img ng-on-load="onImgLoad($event)">
</div>
AngularJS provides specific directives for many events, such as ngClick, so in most cases it is not necessary to use ngOn. However, AngularJS does not support all events and new events might be introduced in later DOM standards.
For more information, see AngularJS ng-on Directive API Reference.

How can I use isolated scope with a component and a directive?

The goal here is to let MainCtrl know when there is an error(s) found in the directive. The error must be displayed here:
<div ng-if="errors[0]">Error 1: {{errors[0]}}</div>
How can I get isolated scope with a directive inside a component? The following application works if you uncomment the 2 lines mentioned below. As it is, I get error:
Multiple Directive Resource Contention
I can read the causes. I need to know how to fix this while still allowing the directive to have isolated scope. I may have 3-4 of these directives on a page and each one needs it's own unique of errors that is also available to the parent.
(working case example on codepen)
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.errors = [false, false];
$scope.text = "bobby";
});
app.directive('testDirective', function(){
return {
restrict: 'A',
scope: {
errors: '=',
text: '#'
},
link: function($scope, $element, $attr, ngModel) {
console.log('link fired');
console.log('errors: ', $scope.errors);
console.log('scope.text', $scope.text);
$attr.$observe('text', function (val) {
if($scope.text === 'bobby'){
$scope.errors[0] = true;
}else{
$scope.errors[0] = false;
}
});
},
template: '<p>text: {{ text }} </p>'
+ '<p>errors: {{errors}}</p>'
+ '<p><input type="text" ng-model="errors" /></p>'
};
});
app.component('panel', {
bindings: {
},
template: [
'<div>',
'</div>'
].join(''),
controller: function() {
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js"></script>
<section ng-app="app" ng-controller="MainCtrl">
<h3>Parent Scope</h3>
<p>errors: {{errors}}</p>
<input type="text" ng-model="text"></div>
<div ng-if="errors[0]">Error 1: {{errors[0]}}</div>
<div ng-if="errors[1]">Error 2: {{errors[1]}}</div>
<!-- UNCOMMENT THE FOLLOWING 2 LINES AND THIS APP WILL WORK
<h3>Directive by itself</h3>
<div test-directive text="{{text}}" errors="errors"><div>
-->
<h3>Directive in component</h3>
<panel test-directive text="{{text}}" errors="errors"></panel>
</section>
After researching, I noticed Angular only returns bool from $validators (as opposed to object). At this point I decided my approach was wrong. I decided to create a unique $valiators for each unique error message. Then use ng-message for the output.
In order to work with multiple components on the same page, I also have to check the ngModel.$error as part of validation. This blog covers the basic approach.

angularjs textarea with colors (with html5 contenteditable)

I'm trying to create an editor which does "syntax highlighting",
it is rather simple:
yellow -> <span style="color:yellow">yellow</span>
I'm also using <code contenteditable> html5 tag to replace <textarea>, and have color output.
I started from angularjs documentation, and created the following simple directive. It does work, except it do not update the contenteditable area with the generated html.
If I use a element.html(htmlTrusted) instead of ngModel.$setViewValue(htmlTrusted), everything works, except the cursor jumps to the beginning at each keypress.
directive:
app.directive("contenteditable", function($sce) {
return {
restrict: "A", // only activate on element attribute
require: "?ngModel", // get ng-model, if not provided in html, then null
link: function(scope, element, attrs, ngModel) {
if (!ngModel) {return;} // do nothing if no ng-model
element.on('blur keyup change', function() {
console.log('app.directive->contenteditable->link->element.on()');
//runs at each event inside <div contenteditable>
scope.$evalAsync(read);
});
function read() {
console.log('app.directive->contenteditable->link->read()');
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if ( attrs.stripBr && html == '<br>' ) {
html = '';
}
html = html.replace(/</, '<');
html = html.replace(/>/, '>');
html = html.replace(/<span\ style=\"color:\w+\">(.*?)<\/span>/g, "$1");
html = html.replace('yellow', '<span style="color:yellow">yellow</span>');
html = html.replace('green', '<span style="color:green">green</span>');
html = html.replace('purple', '<span style="color:purple">purple</span>');
html = html.replace('blue', '<span style="color:yellow">blue</span>');
console.log('read()-> html:', html);
var htmlTrusted = $sce.trustAsHtml(html);
ngModel.$setViewValue(htmlTrusted);
}
read(); // INITIALIZATION, run read() when initializing
}
};
});
html:
<body ng-app="MyApp">
<code contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>This <span style="color:purple">text is purple.</span> Change me!</code>
<hr>
<pre>{{userContent}}</pre>
</body>
plunkr: demo (type yellow, green or blue into the change me input area)
I tried scope.$apply(), ngModel.$render() but has no effect. I must miss something really obvious...
The links I already read through:
others' plunker demo 1
others' plunker demo 2
angularjs documentation's example
$sce.trustAsHtml stackoverflow question
setViewValue stackoverflow question
setViewValue not updating stackoverflow question
Any help is much appreciated. Please see the plunker demo above.
After almost a year, I finally settled to Codemirror, and I was never happier.
I'm doing side-by-side markdown source editing with live update (with syntax highlighting, so even a bit more advanced than stackoverflow's editing page.)
I created a simple codeEditor angular directive, which requires codeMirror, and uses it.
For completeness, here is the component sourcecode:
$ cat components/codeEditor/code-editor.html
<div class="code-editor"></div>
$ cat codeEditor.js
'use strict';
angular.module('myApp')
.directive('codeEditor', function($timeout, TextUtils){
return {
restrict: 'E',
replace: true,
require: '?ngModel',
transclude: true,
scope: {
syntax: '#',
theme: '#'
},
templateUrl: 'components/codeEditor/code-editor.html',
link: function(scope, element, attrs, ngModelCtrl, transclude){
// Initialize Codemirror
var option = {
mode: scope.syntax || 'xml',
theme: scope.theme || 'default',
lineNumbers: true
};
if (option.mode === 'xml') {
option.htmlMode = true;
}
scope.$on('toedit', function () { //event
//This is required to correctly refresh the codemirror view.
// otherwise the view stuck with 'Both <code...empty.' initial text.
$timeout(function() {
editor.refresh();
});
});
// Require CodeMirror
if (angular.isUndefined(window.CodeMirror)) {
throw new Error('codeEditor.js needs CodeMirror to work... (o rly?)');
}
var editor = window.CodeMirror(element[0], option);
// Handle setting the editor when the model changes if ngModel exists
if(ngModelCtrl) {
// Timeout is required here to give ngModel a chance to setup. This prevents
// a value of undefined getting passed as the view is rendered for the first
// time, which causes CodeMirror to throw an error.
$timeout(function(){
ngModelCtrl.$render = function() {
if (!!ngModelCtrl.$viewValue) {
// overwrite <code-editor>SOMETHING</code-editor>
// if the $scope.content.code (ngModelCtrl.$viewValue) is not empty.
editor.setValue(ngModelCtrl.$viewValue); //THIRD happening
}
};
ngModelCtrl.$render();
});
}
transclude(scope, function(clonedEl){
var initialText = clonedEl.text();
if (!!initialText) {
initialText = TextUtils.normalizeWhitespace(initialText);
} else {
initialText = 'Both <code-editor> tag and $scope.content.code is empty.';
}
editor.setValue(initialText); // FIRST happening
// Handle setting the model if ngModel exists
if(ngModelCtrl){
// Wrap these initial setting calls in a $timeout to give angular a chance
// to setup the view and set any initial model values that may be set in the view
$timeout(function(){
// Populate the initial ng-model if it exists and is empty.
// Prioritize the value in ngModel.
if(initialText && !ngModelCtrl.$viewValue){
ngModelCtrl.$setViewValue(initialText); //SECOND happening
}
// Whenever the editor emits any change events, update the value
// of the model.
editor.on('change', function(){
ngModelCtrl.$setViewValue(editor.getValue());
});
});
}
});
// Clean up the CodeMirror change event whenever the directive is destroyed
scope.$on('$destroy', function(){
editor.off('change');
});
}
};
});
There is also inside the components/codeEditor/vendor directory the full codemirror sourcecode.
I can highly recommend codeMirror. It is a rocksolid component, works in
every browser combination (firefox, firefox for android, chromium).

BxSlider and Angular js

If you are working with Angular, probably you will have case where you want to call some JQUERY library to do something for you. The usual way would be to call JQUERY function on page ready:
$(function(){
$(element).someJqueryFunction();
});
If your element content is dynamically added using Angular, than this approach is not good as I understood, and you need to create an custom directive in order to fulfill this functionality.
In my case, I want to load bxSlider on <ul class="bxslider"> element, but content of the <ul> element should be loaded using angular ng-repeat directive.
Picture names are collected using REST service from the database.
I call my directive called startslider using the following code:
<div startslider></div>
My custom Angular directive is: (pictures is variable in the $scope passed from my controller, which called REST service)
application.directive('startslider', function () {
return {
restrict: 'A',
replace: false,
template: '<ul class="bxslider">' +
'<li ng-repeat="picture in pictures">' +
'<img ng-src="{{siteURL}}/slide/{{picture.gallery_source}}" alt="" />'
'</li>' +
'</ul>',
link: function (scope, elm, attrs) {//from angular documentation, the link: function should be called in order to change/update the dom elements
elm.ready(function () {
elm.bxSlider({
mode: 'fade',
autoControls: true,
slideWidth: 360,
slideHeight:600
});
});
}
};
});
As result, I get all the pictures from the database displayed on my screen, but without bxSlider loaded (all pictures displayed one bellow other).
Please note that bxSlider is working, because when I call $(element).bxSlider(); on manually written code, the slider loads.
Why is this happening?
EDIT:
I think your problem is caused by trying to call the slider on HTMLUListElement, which is an object.
To call the slider on the DOM element, you could use $("." + $(elm[0]).attr('class')) that will use the existing bxslider class, or better assign an id to your <div startslider id="slideshow"></div> and call like $("." + $(elm[0]).attr('id'))
elm.ready(function() {
$("." + $(elm[0]).attr('class')).bxSlider({
mode: 'fade',
autoControls: true,
slideWidth: 360,
slideHeight:600
});
});
Full code:
http://jsfiddle.net/z27fJ/
Not my solution, but I'd thought I would pass it along.
I like this solution the best (Utilizes directive controllers)
// slightly modified from jsfiddle
// bxSlider directive
controller: function() {},
link: function (scope, element, attrs, controller) {
controller.initialize = function() {
element.bxSlider(BX_SLIDER_OPTIONS);
};
}
// bxSliderItem directive
require: '^bxSlider',
link: function(scope, element, attrs, controller) {
if (scope.$last) {
controller.initialize();
}
}
http://jsfiddle.net/CaioToOn/Q5AcH/8/
Alternate, similar solution, using events (I do not like)
Jquery bxslider not working + Angular js ng-repeat issue
Root issue
You cannot call scope.$apply in the middle of a digest cycle.
You cannot fire bxSlider() until the template gets compiled.
Ultimately, you need to wait for the template to be compiled and available before calling bxSlider()
Calling a function when ng-repeat has finished
this is the directive
.directive('slideit', function () {
return function (scope, elm, attrs) {
var t = scope.$sliderData;
scope.$watch(attrs.slideit, function (t) {
var html = '';
for (var i = 0; i <= t.length-1; i++) {
html += '<li><ul class="BXslider"><li class="BXsliderHead"><img src="'+scope.$imageUrl+'flight/'+t[i].code+'.gif" />'+t[i].name+'</li><li class="BXsliderChild">'+t[i].noneStop+'</li><li class="BXsliderChild">'+t[i].oneStop+'</li><li class="BXsliderChild">'+t[i].twoStop+'</li></ul></li>';
}
angular.element(document).ready(function () {
$("#" + $(elm[0]).attr('id')).html(html).bxSlider({
pager:false,
minSlides: 3,
maxSlides: 7,
slideWidth: 110,
infiniteLoop: false,
hideControlOnEnd: true,
auto: false,
});
});
});
};
});
this is the html in view
<div ui-if="$sliderData">
<ul id="experimental" slideit="$sliderData"></ul>
</div>
and the important part is the dependency of js file
<script src="/yii-application/frontend/web/js/libraries/angular/angular.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/angular/angular-ui.js"></script>
<script src="/yii-application/frontend/web/js/libraries/jquery/jquery-1.11.3.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/flex/jquery.bxslider.min.js"></script>
<script src="/yii-application/frontend/web/assets/e3dc96ac/js/bootstrap.min.js"></script>
<script src="/yii-application/frontend/web/js/libraries/jquery-ui/jquery-ui.min.js"></script>
<script src="/yii-application/frontend/web/js/searchResult.js"></script>
<script src="/yii-application/frontend/web/js/Flight.js"></script>
and don't forget to put ui in module
var app = angular.module('myApp', ['ui']);
this is the bxslider that i use !!!! maybe solve your problem

unable to make custom html with angular tags work with select2

I am using angular-ui's ui-select2. I want to add custom html formatting to the selections. Select2 allows this by specifying the formatSelection in its config.
I have html with angular tags as below that I want to use for formatting the selection-
var format_code = $compile('<div ng-click="showHide=!showHide" class="help-inline"><div style="cursor: pointer;" ng-show="!!showHide" ng-model="workflow.select" class="label">ANY</div><div style="cursor: pointer;" ng-hide="!!showHide" ng-model="workflow.select" class="label">ALL</div></div>')( $scope );
var format_html = "<span>" + data.n + ' : ' + data.v +' ng-bind-html-unsafe=format_code'+ "</span>"
$scope.select_config = {
formatSelection: format_html
}
If I compile the html as in above and assign it, I just see an [object,object] rendered in the browser. If I dont compile it, I see the html rendered properly, but the angular bindings dont happen, ie the clicks dont work.
Any ideas what is wrong?
I had the same problem, select2 loading in a jquery dialog and not using the options object I would give it.
What I ended up doing is isolating the element in a directive as following:
define(['./module'], function (module) {
return module.directive('dialogDirective', [function () {
return {
restrict: 'A',
controller: function ($scope) {
console.log('controller gets executed first');
$scope.select2Options = {
allowClear: true,
formatResult: function () { return 'blah' },
formatSelection: function () { return 'my selection' },
};
},
link: function (scope, element, attrs) {
console.log('link');
scope.someStuff = Session.someStuff();
element.bind('dialogopen', function (event) {
scope.select2content = MyResource.query();
});
},
}
}]);
and the markup
<div dialog-directive>
{{select2Options}}
<select ui-select2="select2Options" style="width: 350px;">
<option></option>
<option ng-repeat="item in select2content">{{item.name}}</option>
</select>
{{select2content | json}}
</div>
What is important here:
'controller' function gets executed before html is rendered. That means when the select2 directive gets executed, it will already have the select2Options object initialized.
'link' function populates the select2content variable asynchronously using the MyResource $resource.
Go on and try it, you should see all elements in the dropdown as "blah" and selected element as "my selection".
hope this helps, that was my first post to SO ever.

Resources