Kendo UI grid, issue saving new record (AngularJS) - 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.

Related

CKEDITOR Image upload - fileUploadResponse event is not firing

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.

How to uncheck a checkbox that gets filtered in ng-repeat

I've been scratching my head on this one for hours worth of troubleshooting and I can't seem to figure it out so was wondering if any of you could help.
I have an array of objects in a json file, and I'm making a filtering menu based on different properties in the file that one can check/uncheck in view to filter the results. The issue I have is to be able to uncheck any items in the menu that hide as a result of not being available in the current results being displayed.
I have a plunker example here: https://plnkr.co/edit/KZmMiSisA1gKyahG5rHF
Sample from plunker:
$scope.list = [
{ parent : 'fruit', type : 'orange' },
{ parent: 'fruit', type : 'apple' },
{ parent : 'fruit', type : 'kiwi' },
{ parent : 'vegetable', type : 'kale' },
{ parent : 'vegetable', type : 'cabbage' }
];
$scope.filtered = $scope.list;
$scope.selectedType = [];
$scope.selectedParent = [];
$scope.$watch(function () {
return {
selectedType: $scope.selectedType,
selectedParent: $scope.selectedParent,
}
}, function (value) {
var filterType = {
parent : $scope.selectedParent,
type : $scope.selectedType,
};
var startFilter = $scope.list;
for (var i in filterType) {
startFilter = filter(startFilter, filterType[i], i);
}
$scope.filtered = startFilter;
}, true);
Basically, if someone selects "fruit" and then "orange", but then unchecks "fruit", I would want "orange" to uncheck as well.
I just checked your plunker. The code on the bottom is very complicated, but I might be able to help you with these snippets.
Add a ng-change attribute to the parents:
<input type="checkbox"
checklist-model="selectedParent"
checklist-value="key"
data="{{::key}}"
ng-change="checkParent(key, checked)"/>
Now you can detect the changes in your controller:
$scope.checkParent = function(parent, checked) {
if (!checked) {
$scope.list.filter(function(fruit) {
return fruit.parent === parent;
}).forEach(function(fruit) {
$scope.selectedType = $scope.selectedType.filter(function(_selectedType) {
return _selectedType != fruit.type;
});
});
}
};
Plunkr
Beware, that this is inefficient, as it filters Selected type for every fruit to be unselected, it can be refactored with some nice functional tools.
But in general I'd change the controller if possible, and create a map with this structure:
{
parent: {
name: "fruit"
selected: false,
children: [{
type: "organge"
selected: false
}]
...
}
This way you can make your controller code much more readable.
Edit:
I was checking the two filter what you wrote. I couldn't come up with a better code as I still think that you should change the data structure. Iterating over and over lists is an expensive process, and both of your filters has two nested for loops. I cannot think of an easy way of getting rid of them with your data structure.
I spent some time on refactoring your code, getting rid of the watches and utilizing lodash. Check the updated Plunk, I hope it helps.
I added this function to your plunker:
$scope.uncheck = function(key){
$scope.selectedType.splice(key)
}
And this to the parent:
<input type="checkbox" checklist-model="selectedParent" checklist-value="key" data="{{::key}}" ng-change="uncheck(key)" />
It works for me if this is in fact what you are trying to accomplish.

angular scope object issue

PROBLEM:
I created one var object and inside that var object i am referring
to slider scope object. so when I will use my slider it will update
scope object but not update in reference object. i.e
filterObject.filter.priceRange.min
so for changin var object i have to do it manually then it will work
you can see my fiddle so you will understand what i am trying to say because i don't know how to explain my problem.
Workinh example:
http://jsfiddle.net/kevalbhatt18/wk6xhy3k/4/
see what i did:
// this object is for slider
$scope.priceRangeSlider = {
minValue: 100,
maxValue: 10000,
options: {
floor: 100,
ceil: 10000,
step: 1,
translate: function(value) {
return 'Rs ' + value;
}
}
};
//var object refere to scope object
var filterObject = {
filter: {
priceRange: {
min: $scope.priceRangeSlider.minValue // refe not working
},
yearRange: {},
languageValue: [],
formatValue: []
}
};
HOW YOU DEBUG:
slide "price slider" so you can se changeing value below the yearslider, after changing value of price slider click on button below the price slider you will get 100 which is allocated at onload of application
The reason to the problem is that filterObject.filter.priceRange.min will be evaluated only once, when the controller is run for the first time.
One fix is to change it to a function:
var filterObject = {
filter: {
priceRange: {
min:function () { return $scope.priceRangeSlider.minValue; }
...
Then:
$scope.minvaluetest = filterObject.filter.priceRange.min();
Fiddle: http://jsfiddle.net/masa671/9kjqeueo/
UPDATE:
You just need to turn the logic the other way around.
See a new Fiddle: http://jsfiddle.net/masa671/z5fkvh5b/
Now filterObject has the value that can be sent to a server.

How to populate add-row form programmatically for Kendo UI grid (AngularJS)

I have an order line grid where I need to be able to open the popup editor form programmatically with the edit form fields pre-populated (using AngularJs).
In the HTML, I have a lineGrid and an addButton, which calls addRow() on the ticketEntryController:
<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>
</div>
Here is the ticketEntryController:
(function () {
'use strict';
angular.module('app').controller('ticketEntryController', ticketEntryController);
function ticketEntryController($scope) {
$scope.lineGrid = {};
$scope.addRow = function () {
var item = { itemNo: 'TEST123', itemDescr: 'Some description' };
$scope.$broadcast('AddRow', item);
}
}
})();
Here is part of the ticketLineController:
function ticketLineController($scope) {
$scope.$on('AddRow', function(event, item) {
console.log("ticketLineController, AddRow: " + item.itemNo);
$scope.itemNo = item.itemNo;
$scope.itemDescr = item.itemDescr;
$scope.ticketLineGrid.addRow();
});
Plunker: http://plnkr.co/edit/VG39UlTpyjeTThpTi4Gf?p=preview
When the Add Row button is clicked, the editor popup form opens up, but all fields are empty. How can I populate the fields (like they are when you click the Edit button for an existing row)?
I figured out how to get a row to be pre-populated for you, although I'm not sure if this is necessarily the best way to do it, but it does accomplish the job - I'm more familiar with AngularJs, not so much with Kendo UI.
The only place that the Kendo API allows you to change/set the new item that you are adding is in the edit event but I couldn't see a way to send your own object along to the event when you call addRow so you need to have a reference to a shared object in your controller with I called itemForAdd. Before calling addRow() in your controller, you need to set the itemForAdd object with the actual object that you want to pre-populate the form with.
var itemForAdd = {};
$scope.$on('AddRow', function(event, item) {
// save reference to item to use for pre-population
itemForAdd = item;
$scope.ticketLineGrid.addRow();
});
Now in the edit event that the Kendo API sends out, you can populate the items from your selected item in the model item. It's not really required, but I also like to clear out objects that I use like this so in the save and cancel events, I clear out the shared itemForAdd object.
edit: function (e) {
if (e.model.isNew()) {
e.model.set("itemNo", itemForAdd.itemNo);
e.model.set("itemDescr", itemForAdd.itemDescr);
}
var popupWindow = e.container.getKendoWindow();
e.container.find(".k-edit-form-container").width("auto");
popupWindow.setOptions({
width: 640
});
},
save: function(e) {
if (e.model.isNew()) {
// clear out the shared object
itemForAdd = {};
}
},
cancel: function(e) {
if (e.model.isNew()) {
// clear out the shared object
itemForAdd = {};
}
}
With the previous changes, the functionality that you want is mostly working but the data in the table in the edit popup doesn't show the updated values. This is because the Kendo data bindings apparently didn't know they had to update. I couldn't figure out how to make that work, so I just used the AngularJs style bindings for that table (where you had +=itemNo=+), so that the values in the table would update based on the changes in the model object:
<tbody>
<tr>
<td>{{dataItem.itemNo}}</td>
<td>{{dataItem.itemDescr}}</td>
<td>{{dataItem.cat}}</td>
<td>{{dataItem.mfg}}</td>
<td>{{dataItem.mfgPartNo}}</td>
</tr>
</tbody>
But there was one more issue at this point, only the itemNo was being updated, not the itemDescr and that was because itemDescr was set as editable: false in your grid configuration, so I had to changed it to editable: true
fields: {
id: { type: "string", editable: false },
itemDescr: { type: "string", editable: true },
...
},
And finally, here is an updated plunker with my changes: http://plnkr.co/edit/rWavvMh4dRFAsJjuygQX?p=preview

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.

Resources