CKEDITOR Image upload - fileUploadResponse event is not firing - angularjs

I am trying to integrate CKEditor in Angular App. In CKEditor, I am trying to use uploadimage. In run method of my app I have written following code to listen the events of CKEditor.
CKEDITOR.on( 'instanceCreated', function( event ) {
console.log("CKEditor instance created");
});
CKEDITOR.on( 'fileUploadResponse', function( evt ) {
// Prevent the default response handler.
console.log("Image Uploaded");
evt.stop();
// Ger XHR and response.
var data = evt.data,
xhr = data.fileLoader.xhr,
response = xhr.responseText.split( '|' );
if ( response[ 1 ] ) {
// Error occurred during upload.
data.message = response[ 1 ];
evt.cancel();
} else {
data.url = response[ 0 ];
}
console.log("Image Uploaded");
} );
In console it is printing CKEditor instance created, but not printing Image Uploaded. Somehow it is not listening to fileUploadResponse event.
My config file of CKEditor is as follow:
CKEDITOR.editorConfig = function( config ) {
// Define changes to default configuration here.
// For complete reference see:
// http://docs.ckeditor.com/#!/api/CKEDITOR.config
// The toolbar groups arrangement, optimized for two toolbar rows.
config.toolbarGroups = [
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'forms' },
{ name: 'tools' },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'others' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'styles' },
{ name: 'colors' },
{ name: 'about' }
];
// Remove some buttons provided by the standard plugins, which are
// not needed in the Standard(s) toolbar.
config.removeButtons = 'Underline,Subscript,Superscript';
// Set the most common block elements.
config.format_tags = 'p;h1;h2;h3;pre';
// Simplify the dialog windows.
config.removeDialogTabs = 'image:advanced;link:advanced';
config.extraPlugins = 'uploadimage';
config.uploadUrl = '/notice/fileupload';
};
Everything is working fine and my image file is also uploading successfully and I am getting following JSON response:
{
"uploaded": 1,
"fileName": "checkout.PNG",
"url": "/img/syllabus/checkout.PNG",
"error": null
}
But fileUploadResponse is not firing after so many tries. I am not sure which part I am missing.

I think the 'fileUploadResponse'-Event has to be registered on the ckeditor-instance and not on CKEDITOR itself.
var editor = $( 'textarea#editor1' ).ckeditor();
editor.on( 'fileUploadResponse', function( evt ) {...});

Thanks, #Benjamin Schüller for pointing in the right direction.
I am using ng-ckeditor library for CKEditor Textarea along with ng-model data. This library has the directive in which they are initiating the CKEditor instance. All I needed is to get that instance and register fileUploadResponse event to it.
Following is my textarea in template html:
<textarea id="noticeDetails" ckeditor="editorOptions" name="description" ng-model="ctrl.notice.description" ></textarea>
And in my Angular Controller, I am defining editorOptions and binding fileUploadResponse:
$scope.editorOptions = {
language: 'en',
allowedContent: true,
entities: false
};
$scope.$on("ckeditor.ready", function( event ) {
var noticeCkEditor = CKEDITOR.instances["noticeDetails"];
noticeCkEditor.on( 'fileUploadResponse', function( evt ) {
// Prevent the default response handler.
evt.stop();
// Get XHR and response.
var data = evt.data,
xhr = data.fileLoader.xhr,
response = xhr.responseText;
var respJson = angular.fromJson(response);
console.log(respJson);
if ( respJson.error ) {
// Error occurred during upload.
data.message = respJson.error.message;
evt.cancel();
} else {
data.url = respJson.url;
}
} );
});
Following is my JSON response on file upload:
{
"uploaded": 1,
"fileName": "IMG_1202.PNG",
"url": "/img/society/notice/IMG_1202.PNG",
"error": null
}
Few things to note here:
You can get an instance after CKEditor completely initialized. ng-ckeditor has broadcast called ckeditor.ready. So on ckeditor.ready you can get an instance and bind events specific to the editor.
CKEditor gives name to the instance using id of the textarea. In my case id is noticeDetails, so it will create an instance with name noticeDetails. In case you have not given the id then it will create the instance with names editor1, editor2 and so on. In my case, I am getting the CKEditor instance with noticeDetails name.
CKEditor documentation has mentioned example code to handle file upload response manually. But it is not working. They are binding whole JSON string to data.message or data.url which is not the way to do as per my experiment. What we need to do is create the JSON object from the response string and appropriately get the message or URL from that JSON object and bind it with data object as shown in the above code.

Related

Is there a way to open new tab in navigation bar for custom lightning component with record name as tab label?

I need to open the new navigation tab in navigation bar in Lightning Experience in Non Console App environment.Tab should have record name pre-populated as label.
Tried following approach:
Created custom tab for target lightning component
In Source Component:
Created Page Reference with type as standard__navItemPage.
for attributes specified custom tab name for target component.
Using navigation service redirected the control to new URL.
In Target Component:
Using interface isUrlAddressable to retrieve the page param.
var pageReference = {
type: 'standard__navItemPage',
attributes: {
apiName: 'Product_Overview',
},
state: {
c__productId: itemId,
c__isfavourite : isfavourite,
c__isSourceSearchResultCmp : false
}
};
var navService = component.find("navService");
navService.generateUrl(pageReference)
.then($A.getCallback(function(url) {
console.log('Using Navigate'+url);
navService.navigate(pageReference);
}), $A.getCallback(function(error) {
console.log(error);
}));
The issue is , the navigation tab which is getting open is not having details like record name and I could not find any API or methods the same.
Any guidance here would be appreciated.
var pageReference = {
type: 'standard__navItemPage',
attributes: {
apiName: 'Product_Overview',
},
state: {
c__productId: itemId,
c__isfavourite : isfavourite,
c__isSourceSearchResultCmp : false
}};
var navService = component.find("navService");
navService.generateUrl(pageReference).then($A.getCallback(function(url) {
console.log('Using Navigate'+url);
//---add this line which allows you to open url in new tab instead of navService
window.open('https:'+url,
'_blank' // <- This is what makes it open in a new window.
);
}),$A.getCallback(function(error) {
console.log(error);
}));
navigateToRecord(event) {
this[NavigationMixin.GenerateUrl]({
type: 'standard__recordPage',
attributes: {
recordId: event.target.name,
objectApiName: 'Product2',
actionName: 'view',
},
}).then((url) => {
window.open(url);
});
}
If you encounter any issue, please let me know.

Kendo UI grid, issue saving new record (AngularJS)

I have an order line grid with a custom edit form, whose fields are pre-populated for adding a row. I thought I had this working based on help I received from this question:
How to populate add-row form programmatically for Kendo UI grid (AngularJS)
However, though it works in the simplified plunker, there are a couple of issues when trying to implement it in a real project.
Here is an updated plunker to show the issues below:
http://plnkr.co/edit/wtW4RzVu7uuhrJJbWvVd?p=preview
Here is the relevant HTML:
<div id="wrapper" class="container-fluid" ng-controller="ticketEntryController">
<div ng-controller="ticketLineController">
<div kendo-grid="ticketLineGrid" k-options="getTicketLineGridOptions()"></div>
</div>
<button id="addButton" ng-click="addRow()" class="btn btn-primary btn-sm">Add Row</button>
Clicking the addButton button calls $scope.addRow on the ticketEntryController:
(function () {
'use strict';
angular.module('app').controller('ticketEntryController', ticketEntryController);
function ticketEntryController($scope) {
$scope.lineGrid = {};
$scope.addRow = function () {
var item = {
itemNo: "TEST 123",
id: 0,
itemDescr: "new item description",
cat: "CAM",
mfg: "ACME",
mfgPartNo: "ABC123456",
itmStat2: "N",
price: 133,
qty: 1
};
var ticketId = 200;
$scope.$broadcast('AddRow', ticketId, item);
}
}
})();
addRow() above broadcasts to $scope.$on in ticketLineController:
(function () {
'use strict';
angular.module('app').controller('ticketLineController', ticketLineController);
function ticketLineController($scope) {
$scope.$on('AddRow', function(event, ticketId, item) {
console.log("ticketLineController, AddRow: " + item.itemNo);
$scope.ticketId = ticketId;
$scope.itemForAdd = item;
$scope.ticketLineGrid.addRow();
});
$scope.getTicketLineGridOptions = function () {
return {
dataSource: {
type: "json",
transport: {
read: function (options) {
console.log("--- read ---");
options.success(ticketLines);
},
create: function (options) {
console.log("--- create ---");
ticketLines.push(options.data);
options.success(options.data);
},
update: function (options) { // Why is it calling "update" for addRow??
console.log("--- update ---");
ticketLines.push(options.data);
options.success(options.data);
},
destroy:function (options) { // Why is it calling "destroy" for addRow (issue 2)?
console.log("--- destroy ---");
},
},
schema: {
model: {
id: "id",
fields: {
id: { type: "string" },
orderId: { type: "number" },
lineNo: { type: "number" },
...
},
}
},
sort: [{ field: "ItemNo", dir: "asc" }],
pageSize: 50
},
...
edit: function (e) {
if (e.model.isNew()) {
e.model.set("orderId", $scope.ticketId);
e.model.set("lineNo", 0);
e.model.set("id", $scope.ticketId + "_0");
...
e.model.set("qty", 1);
}
var popupWindow = e.container.getKendoWindow();
e.container.find(".k-edit-form-container").width("auto");
popupWindow.setOptions({
width: 640
});
},
Issue #1: When adding a row, "update" is getting called instead of "create" on the grid's dataSource.
Issue #2: After cancelling out of the edit form, the next time you try to add a row, it for some reason calls "destroy" after the "update" To reproduce:
1) Click Add Row
2) Click Cancel in the edit form
3) Click Add Row again
4) Click Update
I heard back from Telerik on this, and the reason "update" was being called instead of "create" is that the id field must be empty for a new record (=0 for integer or "" for string id fields). Once I fixed that, both issues were resolved.
On a related note, the record returned from the server POST (to add record) must contain a populated id field, so that subsequent edits call the "update" instead of "create" in the grid.
I had exactly the same issue. Actually The ID field is autogenerated in my database and the issue was resolved simply by assigning newly created id back to the ViewModel as below:
dbContext.Shipping.Add(entity);
dbContext.SaveChanges();
//int newID = entity.Id;
ShippingViewModel.Id = entity.Id;
Hope this would help.

How to automatically focus first backbone-forms input field?

The following screenshot shows a combined form for sign-in and sign-up:
The following module is used to render the AuthView:
MyApp.module("User", function(User, App, Backbone, Marionette, $, _) {
User.AuthView = Marionette.ItemView.extend({
className: "reveal-modal",
template: "user/auth",
ui: {
signInForm: "#signin-form",
signUpForm: "#signup-form"
},
events: {
"focus input": "onFocus"
},
onFocus: function() {
console.log("Some input field has received focus.");
},
onRender: function() {
this.signInForm = new Backbone.Form({
schema: {
signInEmail: {
type: "Text",
title: "E-Mail address"
},
signInPassword: {
type: "Password",
title: "Password"
}
}
}).render();
this.ui.signInForm.prepend(this.signInForm.el);
this.signUpForm = new Backbone.Form({
schema: {
signUpEmail: {
type: "Text",
title: "E-Mail address"
},
signUpPassword: {
type: "Password",
title: "Password"
},
signUpPasswordConfirmation: {
type: "Password",
title: "Password confirmation"
}
}
}).render();
this.ui.signUpForm.prepend(this.signUpForm.el);
}
});
});
How can I automatically focus the first field in each sub-form whenever it is rendered? The first fields would be signInEmail for the signInForm and signUpEmail for the signUpForm.
I tried to listen to focus input events. Such an event is triggered when I click into one of the input fields, not before.
Meanwhile, inspired by the current answers I came up with the following helper function:
focusFirstFormField: function(form) {
if (_.isUndefined(form)) {
throw "IllegalStateException: Form is undefined."
}
// TODO: AuthView does not focus first sign-in field.
var firstInput = form.find(':input:first');
if (_.isObject(firstInput)) {
if (firstInput.length > 0) {
firstInput = firstInput[0];
}
else {
throw "IllegalStateException: Form find returns an empty jQuery object."
}
}
_.defer(function() {
firstInput.focus();
});
}
There is still need for improvement, though.
The events object are DOM events which are generally triggered by the user so that's not what you'll likely want to use in this case.
If I'm understanding you correctly you would like to put the focus in the first input of each of the forms but since you can only have focus on one thing at a time and they are rendering together you'll have to choose one or the other.
The simplest option is to add another line at the end of onRender focusing on the input. If your input is generating an input something like this:
<input type="text" name="signInEmail">
Then you can add:
this.$('[name=signInEmail]').focus();
If not you'll have to change the selector this.$(xxxx).focus() to suit.
You can use onDomRefresh event of the view. It will be triggered after view rendered and Dom refreshed.
onDomRefresh: function() {
this.focusFirstInput();
};
focusFirstInput: function() {
this.$(':input:visible:enabled:first').focus();
};
This solution applies to general cases. However, pay attention if you are using Bootstrap. I can't get this work there. Instead, I set autofocus: 'autofocus' in the field and it works.
You can add it to onRender method.
this.ui.signInForm.find('input[type=text]:first').focus();
this.ui.signUpForm.find('input[type=text]:first').focus();
On the onRender method I do :
$(this.el).find(':input[autofocus]').focus();
And I add the autofocus="" flag onto my HTML node. This works for refresh.

Handling Subsidiary Views in Backbone.js

I have a basic Backbone application which obtain an array of JSON objects from a remote service and displays them: all good so far. However, each JSON object has an array of tags and I want to display the tags in a separate area of the webpage.
My question is: what is the most Backbone-friendly way of doing this? I could parse the existing data again in a second view, which is cleaner but takes up more computation (processing the entire array twice).
An alternative is gathering up the tag information in the primary view as it is working through the array and then passing it along to the subsidiary view, but then I'm linking the views together.
Finally, I'd like to filter based on those tags (so the tags will become toggle buttons and turning those buttons on/off will filter the information in the primary view); does this make any difference to how this should be laid out?
Bonus points for code snippets.
Hm. I'm not sure if this is the Backbone-friendly way, but I'll put the logic to retrieve a list of tags (I think that's what you meant by "parse") in the collection.
Both the main view and the subview will "listen" to the same collection, and the subview will just call collection.getTags() to get a list of tags it needs.
// Model that represents the list data
var ListDataModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
tags: []
};
}
});
// Collection of list data
var ListDataCollection = Backbone.Collection.extend({
model: ListDataModel,
initialize: function() {
var me = this;
// Expires tag collection on reset/change
this.on('reset', this.expireTagCache, this);
this.on('change', this.expireTagCache, this);
},
/**
* Expires tag cache
* #private
*/
expireTagCache: function() {
this._cachedTags = null;
},
/**
* Retrieves an array of tags in collection
*
* #return {Array}
*/
getTags: function() {
if (this._cachedTags === null) {
this._cachedTags = _.union.apply(this, this.pluck('tags'));
}
return this._cachedTags;
},
sync: function(method, model, options) {
if (method === 'read') {
var me = this;
// Make an XHR request to get data for this demo
Backbone.ajax({
url: '/echo/json/',
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
json: JSON.stringify([
{ id: 1, name: 'one', tags: [ 'number', 'first', 'odd' ] },
{ id: 2, name: 'two', tags: [ 'number', 'even' ] },
{ id: 3, name: 'a', tags: [ 'alphabet', 'first' ] }
]),
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
var listColl = new ListDataCollection();
listColl.fetch({
success: function() {
console.log(listColl.getTags());
}
});
I guess two reasons for handling this in the collection:
It keeps the View code cleaner (This is given that we are not doing very complex logic in the tag extraction - It's just a simple _.pluck() and _.union().
It has 0 business logic involved - It can arguably belong to the data layer.
To address the performance issue:
It does go through the collection twice - However, if the amont of data you are consuming is too much for the client to process even in this case, you may want to consider asking the Backend to provide an API endpoint for this. (Even 500 pieces of data with a total of 1000 tags shouldn't bee too much for a somewhat modern browser to handle nowadays.)
Hmm. Does this help?
JSFiddle to go with this with the collection and the model: http://jsfiddle.net/dashk/G8LaB/ (And, a log statement to demonstrate the result of .getTags()).

How to populate an Ext.data.TreeStore without children or leaf information?

I have a service that returns an array of Things. A Thing simply has an id and a name.
I want to load these into an Ext.tree.Panel. For now, I've defined a data store that mimics a typical response from the service. You can play with a demo JSFiddle here.
Code included below as well:
Ext.define('Thing', {
extend: 'Ext.data.Model',
fields: ['id', 'name'],
});
var store = Ext.create('Ext.data.TreeStore', {
model: 'Thing',
// example service response (in reality, this would be JSON)
root: {
children: [
{
id: 1,
name: 'Thing 1',
},
{
id: 2,
name: 'Thing 2',
},
{
id: 3,
name: 'Thing 3',
},
],
},
listeners: {
append: function(thisNode, newChildNode, index, eOpts) {
if( !newChildNode.isRoot() ) {
newChildNode.set('text', newChildNode.get('name'));
}
},
},
});
Ext.create('Ext.tree.Panel', {
renderTo: Ext.getBody(),
store: store,
rootVisible: false,
});
As you can see, the service only returns a Thing's id and name, no tree node information like children or leaf, which is what an Ext.data.TreeStore normally expects.
I simply cannot change what the service returns as a response. I like that I can call the service and get a flat array of Things, without extraneous tree node information. There are many apps talking to this service that simply do not need such data, and I don't have permission from the powers-that-be to change it.
Unfortunately, without the children element defined for each Thing in the response, Ext raises the following error when I attempt to expand a node (you can produce this yourself in the JSFiddle):
Uncaught TypeError: Cannot call method 'indexOf' of undefined
So, my question is: how can I avoid having to send back children (or leaf) in my response and resolve this error? I'd simply like my Things to be loaded into the tree and to be expandable (yes, they can't be leaves).
Add those information by yourself!
Little example (You easily can try it in the console from your browser):
a = {b:23};
a.c = 25;
//a is now the same like {b:23, c:25}
In the afterRequest-method of the store you can for example do following:
proxy: {
type: 'ajax',
url: 'xxx',
listeners: {
exception: function(proxy, response, options) {
//error case
}
},
afterRequest: function(request, success) {
var resText = request.operation.response.responseText,
allThings = Ext.decode(resText), //Decodes (parses) a JSON string to an object
things = allThins.children, //Now we have an object an we can do what we want...
length = things.length;
for (var i = 0; i < length; i++) {
things[i].leaf = true;
things[i].expanded = true;
}
this.setRootNode(allThings);
}
}
This is just some pseudo-code, but it should work!
Set a debugger Statement (Link) and see what you are really getting back!
I hope this helps!
You might try iterating over your response store and creating children nodes.
Then append your children to your root node
store.each(function(rec) {
var childNode ;
//Create a new object and set it as a leaf node (leaf: true)
childNode = Ext.create('Model_Name', {
id: rec.data.id,
name: rec.data.name,
text : rec.data.name,
leaf : true,
});
// add/append this object to your root node
treepanel.getRootNode().appendChild(childNode );
});
Check this link for more details

Resources