How can I use AngularJS data binding with CKEditor? - angularjs

How should I go about this?
I was able to get the data into CKEditor by using a textarea with the name attribute matching my model and a script tag with ng:bind-template to call CKEDITOR.replace.
I then made a CKEditor plugin that detects changes and writes them back to the textarea. The problem is that the textarea looses its event handlers when CKEditor initializes and CKEditor doesn't pickup changes to the textarea. This makes me think that I am approaching this the wrong way.
Next I tried using ng:eval-order="LAST" ng:eval="setupCKEditor()" and setting up the editor from the setupCKEditor() function. This didn't work because even with ng:eval-order="LAST" the function is still run before the nodes are created.
I have found that adding a setTimeout(function () {...},0) around the CKEDITOR.replace helps. Now the only problem is that when it changes the model it doesn't repaint the screen until another field is edited.
scope.$root.$eval(); seems to fix that.
Update
We ended up abandoning this since we could never get it to reliably work. We switched to TinyMCE with Angular-UI for a while and then ended up building something custom.

This sort of works with the onchange plugin from http://alfonsoml.blogspot.com/2011/03/onchange-event-for-ckeditor.html.
angular.directive("my:ckeditor", function (expression, compiledElement) {
return function fn(element) {
var scope = this;
setTimeout(function () {
CKEDITOR.replace("editor-" + index, {extraPlugins: 'onchange'});
scope.$watch("layers[" + index + "].src", function () {
if (!CKEDITOR.instances["editor-" + index]) return;
if (scope[expression] == CKEDITOR.instances["editor-" + index].getData()) return;
CKEDITOR.instances["editor-" + index].setData(scope[expression]);
});
CKEDITOR.instances["editor-" + index].on("change", function () {
scope[expression] = CKEDITOR.instances["editor-" + index].getData();
scope.$root.$eval();
});
}, 0);
};
});
Update
This has only been tested on v0.10.6

For completeness, attached is a module to provide an angular directive. I've not used it yet, so I can not comment on how well it works/integrates.

Related

How $watch changes of a variable in a service from component's controller?

I have been through all related topics on SO, namely these two:
$watch not detecting changes in service variable
$watch not detecting changes in service variable
are tackling the same issue, but i failed to make it working. Unlike in the above cases, I am using a controller from a component, hence maybe this is related to lacking binding in components, idk. Hope for some experinced assistance.
Have a service:
(function (angular) {
'use strict';
angular
.module('Test')
.service('ShareData', ShareData);
ShareData.$inject = [];
function ShareData() {
let vm = this;
vm.indexes = [];
vm.setIndexes = function(firstIndexParam, lastIndexParam, message) {
if (leaderIndexParam !== undefined || partnerIndexParam !== undefined) {
vm.indexes.mainIndex = firstIndexParam;
vm.indexes.secondaryIndex = lastIndexParam;
vm.indexes.message = message;
}
};
vm.getIndexes = function() {
return vm.indexes;
};
}
})(angular);
It is used in 3 components. Two of them are sending data into the service, the third one uses this data. Sending of data is accomplished in the following way, works:
ShareData.setIndexes(firstIndex, secondIndex, 'update_indexes');
Now here is my problem. In main parent controller i can comfortably access the data by
ShareData.getIndexes();
But my issue is that I need changes in indexes to trigger certain actions in parent controller, so I tried so do as stipulated by relevant questions here on SO:
$scope.$watch('ShareData.getIndexes()', function(newVal) {
console.log('New indexes arrived', newVal);
});
In main controller, I am injecting the service:
TabController.$inject = ['ShareData'];
and using it like:
let indexService = ShareData.getIndexes();
As i said, I can get the data when I am explicitly calling the function. The issue is that it needs to be triggered by the service itself.
It does not work regardless of shamanistic ceremonies a made :) Now, obviously, I am missing something. Should I somehow bind this service to the component, and if yes how is it done? Or maybe the solution is totally dysfunctional and impossible to achieve in my circumstances? An advise is appreciated!
UPDATE: I already have a functional solution with the same service working with $rootScope.$broadcast, however my aim is to get rid of it and not work with the $rootScope.
The problem is that you never actually change the value of vm.indexes - it always points to the same array. setIndexes only modifies properties of this array. That's why $watch, which by default checks for reference equality only, fails to spot the changes.
There are (at least) two ways of solving this: either make $watch check the object equality instead, by adding a third param there:
$scope.$watch('ShareData.getIndexes()', function(newVal) {
console.log('New indexes arrived', newVal);
}, true);
... or (better, in my opinion) rewrite the set function so that it'll create a new instance of indexes instead when there's a change:
vm.setIndexes = function(firstIndexParam, lastIndexParam, message) {
if (leaderIndexParam === undefined && partnerIndexParam === undefined) {
return;
}
vm.indexes = vm.indexes.slice();
Object.assign(vm.indexes, {
mainIndex: firstIndexParam,
secondaryIndex: lastIndexParam,
message: message
});
};
As a sidenote, simply calling setIndexes() does not trigger the digest - and $watch listener only checks its expression when digest is triggered.

Protractor - Unable to access element due to fixed Top navigation bar

I'm facing the following issue in protractor with jasmine
Click/mouse hover not working because of fixed top navigation bar in my application. I need to click/perform mouse hover on a web page.
Unfortunately that element is displaying behind that fixed navigation bar. So scroll till element present & click by x & y coordinates are not working.
My dependencies are :
protractor version 5.2.2
node 8.9.3
selenium standalone 3.13
chrome driver-2.40
chromebrowser v67
OS- Windows 10
Thanks in advance
Try using prototype executeScript
Just try clicking that element from the browser console using id,name or xpath.
For example :
var el = element(by.module('header'));
var tag = browser.executeScript('return arguments[0].click()', el).then(function() {
expect(something).toMatch(something);
});
Another way, along the same lines as what Bharath Kumar S and knowing JeffC's caveat that this approach is cheating, I had a similar issue where the App-Header kept getting in my way of clicking, and I knew I was willing to never need it (so, for instance, to find other ways to navigate or log out and not check for stuff that was on it). I, therefore, did the following, which solved the problem. Note if you refresh the screen, you have to call it again. Also note I am using a number of functions from https://github.com/hetznercloud/protractor-test-helper, which do what you would expect from their names.
var removeAppHeaderIfAny = async function() {
//this function hides the app header
//it is useful to avoid having covers there when Protractor worries that something else will get the click
let found = false;
try {
found = await waitToBeDisplayed(by.className("app-header"), 2000);
} catch (e) {
let s: string = "" + e;
if (s.search("TimeoutError") != 0) flowLog("presumably fine, cover already removed: " + e);
found = false;
}
if (!found) return;
if (found) {
let coverElement = await element(by.className("app-header"));
browser.executeScript(
"arguments[0].style.visibility='hidden';",
coverElement
);
await waitToBeNotDisplayed(by.className("app-header"), 10000);
}
return;
//note after this is called you will not see the item, so you cannot click it
};
As I look at the code, it strikes me one can probably remove the if (found) and associated brackets at the end. But I pasted in something I know has been working, so I am not messing with that.
As indicated up front, I knew I was willing to forego use of the app-header, and it is a bit crude.

Publish/Subscribe not working automatically when data added to the mongodb

I have the following publisher and subscriber code.
It works for the first time when the app starts, but when I try to insert data directly into the Mongo database, it will not automatically update the user screen or I don't see the alert popping.
Am I missing something?
Publish
Meteor.publish('userConnections', function(){
if(!this.userId){
return;
}
return Connections.find({userId: this.userId});
})
Subscribe
$scope.$meteorSubscribe('userConnections').then(function () {
var userContacts = $scope.$meteorCollection(Connections);
alert("subscriber userConnections is called");
if (userContacts && userContacts[0]) {
....
}
}, false);
First off, if you are not using angular-meteor 1.3 you should be. The API has changed a lot. $meteorSubscribe has been deprecated!
To directly answer your question, $meteorSubscribe is a promise that gets resolved (only once) when the subscription is ready. So, it will only ever be called once. If you look at the documentation for subscribe you'll see how to make the binding "reactive", by assigning it to a scope variable. In your case it would be something like:
$scope.userContacts = $scope.$meteorCollection(Connections);
Doing it this way, when the collection gets updated, the $scope.userContacts should get updated as well.

Angularjs Passing array between controllers

I have been through several tutorials and posts about this topic and still can't seem to figure out what is wrong with my code. To me it seems I am having scoping issues with the data within my service. My code is split up into separate files. Here is my code:
github link : https://github.com/StudentJoeyJMStudios/PetPinterest.git
//in dataService.js
var app = angular.module('se165PetPinterestWebApp');
app.service('SharedData', function ()
{
var categoryOfSelectedAnimals = [];
this.setCatOfSelAnimals = function(pulledCategoriesFromParse)
{
categoryOfSelectedAnimals = pulledCategoriesFromParse;
console.log('after assignment in set::' + categoryOfSelectedAnimals);
};
this.getCatOfSelAnimals = function()
{
console.log('in get::::' + categoryOfSelectedAnimals);
return categoryOfSelectedAnimals;
};
});
in my first controller to set the data in signup.js
app.controller('SignupCtrl',['$scope', 'SharedData', function ($scope, SharedData)
{
var Categories = Parse.Object.extend('Categories');
var query = new Parse.Query(Categories);
query.find({
success: function(results)
{
$scope.availableCategoriesOfAnimals = results;
SharedData.setCatOfSelAnimals(results);
},
error: function(error)
{
alert('Error: ' + error.code + ' ' + error.message);
}
});
};
}]);
Then in my other controller trying to get the data from the array within my service:
var app = angular.module('se165PetPinterestWebApp');
app.controller('CatSelCtrl', function ($scope, SharedData)
{
$scope.availableCategoriesOfAnimals = SharedData.getCatOfSelAnimals();
});
When I print the contents from the SharedData.getCatOfSelAnimals I get 0 every time. Please help. Thank you very much in advance.
EDIT: After playing around with a string a bit I am finding the changed string in the set function is not saved into the service and when I call my get function within my service the string is not changed from the set method. Please help, thank you in advance.
EDIT: So it looks like when I navigate to new page by using window.location.href = '../views/categorySelection.html'; in my signup.js it reloads my dataService.js which re-sets my variables back to nothing. Does anyone have any ideas as to how to fix this?
Edit
First: why you lose data
You need to setup routing properly. Right now you are not changing views but rather using window.location.href to load a new bootstrap file (dashboard.html), i.e. everything saved in memory will be lost. So you have been doing it right, sort of, but the moment you change to dashboard.html all data from Parse is lost.
You can solve this by configuring routes and use $location.url() to change URL. Read more about angular.route here: https://docs.angularjs.org/api/ngRoute/service/$route
The angular way
After looking at your code and running your app I think we need to take a step back. Angular is tricky to get used to but there is a lot of good tutorials. I think you might wanna read some of them to get a better grasp of how it works and how to setup and build your app.
Start here: http://www.airpair.com/angularjs
Boilerplate
Found this boilerplate for an Angular app using Parse. It might be something you could use. https://github.com/brandid/parse-angular-demo
Original
Or an even quicker way to empty $scope.availableCategoriesOfAnimals and then merge new data without breaking reference:
$scope.availableCategoriesOfAnimals.length = 0;
Array.prototype.push.apply($scope.availableCategoriesOfAnimals, pulledCategoriesFromParse);
You are breaking the reference on assignment. This is a JavaScript issue, not an angular one for that matter ;)
Try this in your set function:
categoryOfSelectedAnimals.length=0;
pulledCategoriesFromParse.forEach(function (e) {categoryOfSelectedAnimals.push(e)});
in stead of reassigning
edit: angular extend works on objects, not arrays, so replaced it with a bit of JS.

jquery chosen.js plugin multiselect does not update

Tried running this on a multi select.
$('#field-participants').trigger('liszt:updated');
without any luck. Not sure what I might be missing. It does work on a non multi select.
The id is of the select element. The new option has been added via ajax.
Docs here say it should work http://harvesthq.github.io/chosen/
Edit:
Adding more code. This code is called by the success handler of my ajax request.
The select does get updated but the chosen part is not refreshed
updateParticipants: function(data) {
var $select = $('#field-participants');
$select.append(
$('<option></option>')
.val(data.value)
.html(data.name));
$select.trigger('liszt:updated');
}
They way you handle the liszt:updated is correct. But there seems to be a small mistake, not sure whether I am correct.
Try the below code.
updateParticipants: function(data) {
$('#field-participants').append('<option value="' + data.value + '">'+ data.name + '</option>');
$('#field-participants').trigger('liszt:updated');
}
Hope this is clear.

Resources