I am in the process of learning Backbone.js and using BackGrid to render data and provide the end user a way to edit records on an Microsoft MVC website. For the purposes of this test grid I am using a Vendor model. The BackGrid makes the data editable by default (which is good for my purpose). I have added the following JavaScript to my view.
var Vendor = Backbone.Model.extend({
initialize: function () {
Backbone.Model.prototype.initialize.apply(this, arguments);
this.on("change", function (model, options) {
if (options && options.save === false) return;
model.url = "/Vendor/BackGridSave";
model.save();
});
}
});
var PageableVendors = Backbone.PageableCollection.extend(
{
model: Vendor,
url: "/Vendor/IndexJson",
state: {
pageSize: 3
},
mode: "client" // page entirely on the client side.
});
var pageableVendors = new PageableVendors();
//{ data: "ID" },
//{ data: "ClientID" },
//{ data: "CarrierID" },
//{ data: "Number" },
//{ data: "Name" },
//{ data: "IsActive" }
var columns = [
{
name: "ID", // The key of the model attribute
label: "ID", // The name to display in the header
editable: false, // By default every cell in a column is editable, but *ID* shouldn't be
// Defines a cell type, and ID is displayed as an integer without the ',' separating 1000s.
cell: Backgrid.IntegerCell.extend({
orderSeparator: ''
})
}, {
name: "ClientID",
label: "ClientID",
cell: "integer" // An integer cell is a number cell that displays humanized integers
}, {
name: "CarrierID",
label: "CarrierID",
cell: "number" // A cell type for floating point value, defaults to have a precision 2 decimal numbers
}, {
name: "Number",
label: "Number",
cell: "string"
}, {
name: "Name",
label: "Name",
cell: "string"
},
{
name: "IsActive",
label: "IsActive",
cell: "boolean"
}
];
// initialize a new grid instance.
var pageableGrid = new Backgrid.Grid({
columns: [
{
name:"",
cell: "select-row",
headercell: "select-all"
}].concat(columns),
collection: pageableVendors
});
// render the grid.
var $p = $("#vendor-grid").append(pageableGrid.render().el);
// Initialize the paginator
var paginator = new Backgrid.Extension.Paginator({
collection: pageableVendors
});
// Render the paginator
$p.after(paginator.render().el);
// Initialize a client-side filter to filter on the client
// mode pageable collection's cache.
var filter = new Backgrid.Extension.ClientSideFilter({
collection: pageableVendors,
fields: ['Name']
});
// REnder the filter.
$p.before(filter.render().el);
//Add some space to the filter and move it to teh right.
$(filter.el).css({ float: "right", margin: "20px" });
// Fetch some data
pageableVendors.fetch({ reset: true });
#{
ViewBag.Title = "BackGridIndex";
}
<h2>BackGridIndex</h2>
<div id="vendor-grid"></div>
#section styles {
#Styles.Render("~/Scripts/backgrid.css")
#Styles.Render("~/Scripts/backgrid-select-all.min.css")
#Styles.Render("~/Scripts/backgrid-filter.min.css")
#Styles.Render("~/Scripts/backgrid-paginator.min.css")
}
#section scripts {
#Scripts.Render("~/Scripts/underscore.min.js")
#Scripts.Render("~/Scripts/backbone.min.js")
#Scripts.Render("~/Scripts/backgrid.js")
#Scripts.Render("~/Scripts/backgrid-select-all.min.js")
#Scripts.Render("~/Scripts/backbone.paginator.min.js")
#Scripts.Render("~/Scripts/backgrid-paginator.min.js")
#Scripts.Render("~/Scripts/backgrid-filter.min.js")
#Scripts.Render("~/Scripts/Robbys/BackGridIndex.js")
}
When the user edits a row, it successfully fires the hits the model.Save() method and passes the model to the save Action, in this case BackGridSave and it successfully saves the record that changed, but seems to save all of the vendors in model when only one of the vendors changed. Is there a way from the JavaScript/Backbone.js/BackGrid to only pass one Vendor - the vendor that changed?
Update: I realized that it is not sending every vendor, but it is sending the same vendor multiple times as though the change event was firing multiple times.
I guess I answered my own question. Well, at least I am getting the desired result. I just added a call to off after the first on. Seems like this would not be necessary though.
var Vendor = Backbone.Model.extend({
initialize: function () {
Backbone.Model.prototype.initialize.apply(this, arguments);
this.on("change", function (model, options) {
if (options && options.save === false) return;
model.url = "/Robbys/BackGridSave";
model.save();
model.off("change", null, this); // prevent the change event from being triggered many times.
});
}
});
Related
I want to display data from bid table in a form of datatable. But I get this error
"Trying to get property 'bids' of non-object" if it doesn't have bids.The bids model is connected to auction model and the auction model is connected to media site model. How to make it display blank record if it doesn't have data.
Here is my controller:
<?php
namespace App\Http\Controllers;
use App\Auction;
use App\Bid;
use App\User;
use App\Media;
use App\MediaSite;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class MediaSiteController extends Controller
{
public function show(MediaSite $mediaSite)
{
$auction = $mediaSite->auction;
$bids = $auction->bids;
return view('admin.media-site.show', ['mediaSite' => $mediaSite,'auction' => $auction], compact('auction'));
}
My view:
<body>
<div id="datatable-bid"></div>
</body>
<script>
$(document).ready(function () {
var datatableBid = $('#datatable-bid').mDatatable({
// datasource definition
data: {
type: 'local',
source: {!! json_encode($auction->bids) !!},
pageSize: 10
},
// layout definition
layout: {
theme: 'default', // datatable theme
class: '', // custom wrapper class
scroll: false,
footer: false // display/hide footer
},
// column sorting
sortable: true,
pagination: true,
search: {
input: $('#panel-search')
},
// columns definition
columns: [
{
field: "price",
title: "Price",
}, {
field: "month",
title: "Month",
},{
field: "user_id",
title: "User Id",
}
]
});
</script>
Here is my error:
Trying to get property 'bids' of non-object
place following after $auction = $mediaSite->auction;
if($auction){
$bids = $auction->bids;
}else{
//put following line or whatever you need to do if there is no data comes
$auction = [];
}
In the show() function make these changes
$auction = $mediaSite->auction;
if($auction) {
$bids = $auction->bids;
} else {
$bids = [];
}
// now send $bids to view along with $auction
// may be like this
// return view(..., compact($auction, $bids));
Then, in your view make this change
// datasource definition
data: {
type: 'local',
source: {!! json_encode($bids) !!},
pageSize: 10
},
See if this helps.
I have a parent view which contains a Backgrid view. In the parent view's initialize section I define a variable isWellSelected. The variable is toggled in the Backgrid column logic when a tickbox is checked. I am able to watch the variable toggled when a box is ticked and unticked.
However, once an event fires the variable is no longer in scope for the event to see. I suspect I may need to pass the variable to the Backrgrid view but I am unsure how to do that correctly. Please advise.
app.wellCollectionView = Backbone.View.extend({
template: _.template($('#wellTemplate').html()),
initialize: function() {
this.isWellSelected = false;
// isWellSelected toggled to true when a tickbox is checked in the columns block.
var columns = [...];
// instantiate collection
var wellCollection = new app.wellCollection;
// Set up a grid view to use the pageable collection
var wellGrid = new Backgrid.Grid({
columns: columns,
collection: wellCollection
});
// Initialize the paginator
var paginator = new Backgrid.Extension.Paginator({
collection: wellCollection
});
// Render the template
this.$el.html(this.template());
// Render the grid
this.$el.append(wellGrid.render().el);
this.$el.append(paginator.render().$el);
wellCollection.fetch({reset: true}).then(function () {...});
},
events: {
'click #EvaluateWell': function(){
this.evalWell(event, this.isWellSelected);
console.log("In events - isWellSelected: " + this.isWellSelected);}
},
// More stuff
}
Constructive feedback welcome.
Thanks!
Adding a snippet for "columns" as per JT's request:
var columns = [
{
name: '',
label: 'Select',
cell: Backgrid.BooleanCell.extend({
events : {
'change': function(ev){
var $checkbox = $(ev.target);
var $checkboxes = $('.backgrid input[type=checkbox]');
if($checkbox.is(':checked')) {
$checkboxes.attr("disabled", true);
this.isWellSelected = true;
// Disable all checkboxes but this one
$checkbox.removeAttr("disabled");
} else {
// Enable all checkboxes again
$checkboxes.removeAttr("disabled");
this.isWellSelected = false;
}
}
}
})
}, {
name: "api",
label: "API",
editable: false, // Display only!
cell: "string"
}, {
name: "company",
label: "Operator",
editable: false, // Display only!
cell: "string"
}];
I'm having a table whose contents(name and status) i'm trying to refresh every 1 min in such a way that the status should change from 'schedule' to 'active' to 'running' w/o having user manually refresh it.
here is the js:
$cls.superclass.constructor.call(this, Ext.apply({
useTemplate: true,
directFn: Service('getRecordService').getRecord,
fields: [
{
name : 'name',
column: {
header: "Name",
renderer: function (record) {
return name(record.data.name);
},
}
},
{
name : 'status',
column: {
header: "status",
renderer: function (record) {
return status(record.data.status);
},
}
},
],
viewConfig: {
getRecord: function (){
///get single record info
}
};
});
I'm not sure how can i write a function in the above snippet that will allow me to auto refresh the table contents every 1 min. Any ideas would be appreciated!
Thanks!
You should take a look at Ext.util.TaskRunner and the singleton Ext.TaskMgr.
It provides methods to start/stop task functions which are executed in a configurable interval.
With that you could try something like below.
var grid = ; // your grid instance
var store = grid.getStore(); // store of your grid, you want to reload
var refreshTask = { // task which reloads the store each minute
run: function() {
store.reload();
},
interval: 60 * 1000 // 1 Minute
};
Ext.util.TaskRunner.start(refreshTask);
My problem is that I Have Hierarchical grid (Master and Child) let say I Have a Department Grid it contains List of Employee Grid, and they both use same datasource.
Here's my GridChild Code:
function detailInit (e){
var msterRow = e.sender.items().index(e.masterRow).toString();
var grid = $("<div id='childGrid"+msterRow+"'
class=childGrid'/>").appendTo(e.detailCell).kendoGrid({
data: e.data.DeptEmployees,
schema: {
model: { fields: { foo: {--skip--}, bar: {--skip--} } }
},
toolbar: ["create", "cancel", "save"],
editable: "popup",
columns: [ --skip--]
save: function(e){
ajaxUpdateDepartment(msterRow, this.dataSource.data());
}
})
As you can see i use data: e.data.DeptEmployees, as child data source to fetch data.
Now I'm stacked in how can I update the child data source?
What I have Tried:
I add child's dataSource.transport for updates, but my child grid keeps on loading.
So I end up configuring the save: function (e) and simply send all data source of the current child but popup editor didn't close at all. And I'm having difficulty to refresh the child data source.
I also attempt to convert my Master and Child Grid to ASP Razor but there was no definite example if how could I handle it in back end, and also my child grid contains drop down grid, so that would be a big re-do. And I also don't know if how can I pass customize parameter through it
I am desperate, I can't find any working reference except this one. but it's using odata, and I dont have child id to use as reference, since I am only using list which I retrieve in a user event.
Please help :'( I'm taking too much time for this one.
The solution is to define a transport properties, in order to fetch data from master, I only need to define the data and convert that to Jason.
take a look of these code:
function detailInit (e){
var msterRow = e.sender.items().index(e.masterRow).toString();
var grid = $("<div id='childGrid"+msterRow+"'
class=childGrid'/>").appendTo(e.detailCell).kendoGrid({
//data: e.data.ChildDetails,
transport: {
read: function (o) {
console.log("child read");
var data = e.data.ChildDetails.toJSON();
o.success(data);
},
update: function (o) {
console.log("child update");
var data = o.data,
arentItem = findByID(data.id);
for (var field in data) {
if(!(field.indexOf("_") === 0)){
arentItem[field] = data[field];
}
}
e.data.dirty = true;
saveChild(record, "#suffix", msterRow, "update");
o.success();
},
destroy: function (o) {
var parentItem = findByID(o.data.id);
preventBinding = true;
e.data.ChildDetails.results.remove(parentItem);
o.success();
saveChild(record, "#suffix", msterRow, "destroy");
},
create: function (o) {
console.log("child create");
var record = o.data;
record.id = index;
index++;
saveChild(record, "#suffix", msterRow, "create");
o.success(record);
}
},
schema: {
model: { fields: { foo: {--skip--}, bar: {--skip--} } }
},
toolbar: ["create", "cancel", "save"],
editable: "popup",
columns: [ --skip--]
}
Here's the working dojo snippet
I've got a Backbone Model called Delivery. I then create a collection of Deliveries called DeliveryList backed by LocalStorage. In my Marionette.ItemView for displaying items in the collection, I have a method to remove items:
removeDeliveryOption: function() {
Deliveries.remove(this.model.get("id"));
}
For some reason, this removes the item from the Marionette.CompositeView when I click the remove button, but when I reload the page the same number of items always reappear.
It's worth noting that when I delete the item, it always reappears with the default optionName "Free Delivery". I'm using both defaults and a schema in the model because I'm using the Backbone-forms plugin (https://github.com/powmedia/backbone-forms).
Any help is greatly appreciated!
var Delivery = Backbone.Model.extend({
defaults: function () {
return {
order: Deliveries.nextOrder(),
optionName: "Free Delivery",
shipToState: "Hawaii",
zipCodes: "96813",
perOrderFee: "0.00",
perItemFee: "0.00"
};
},
schema: {
optionName: { type: 'Text', validators: ['required'] },
shipToState: { type: 'Select', options: getStateNames(), validators: ['required'] },
zipCodes: { type: 'Text', validators: ['required'] },
perOrderFee: { type: 'Text', validators: ['required'] },
perItemFee: { type: 'Text', validators: ['required'] },
}
});
var DeliveryList = Backbone.Collection.extend({
model: Delivery,
localStorage: new Backbone.LocalStorage("deliverylist-backbone"),
nextOrder: function () {
if (!this.length) return 1;
return this.last().get('order') + 1;
},
comparator: 'order'
});
var Deliveries = new DeliveryList;
var deliveryView = Marionette.ItemView.extend({
//tagName: "li",
template: "#delivery-item-template",
events: {
"click #removeThis": "removeDeliveryOption",
},
removeDeliveryOption: function() {
Deliveries.remove(this.model.get("id"));
}
});
var DeliveriesView = Marionette.CompositeView.extend({
initialize: function() {
Deliveries.fetch();
},
template: '#deliveries-view-template',
itemView: deliveryView,
events: {
"click #addShipping": "addDeliveryOption",
},
addDeliveryOption: function() {
var editDeliveryForm = new Backbone.Form({
template: _.template($("#editDeliveryTemplate").html()),
model: Deliveries.create()
}).render();
this.$el.append(editDeliveryForm.el);
$("#triggerEditDelivery").fancybox({
'afterClose': function () {
commitForm(editDeliveryForm);
//Wait do display the inlineModel until here
// Once we've bound the form to the model, put the saving logic with the collection
//Deliveries.last().save();
}
}).trigger('click');
},
// Specify a jQuery selector to put the itemView instances in to
itemViewContainer: "#deliveries",
});
EDIT
Thanks to #ejosafat! Had to destroy the model instead of just removing from collection.
removeDeliveryOption: function() {
this.model.destroy();
}
The remove method only affects the collection loaded in the browser, not in the permanent storage (local or server). That's why it dissappears from the view but when you reload the page it appears again.
If you want to get rid of that model in the storage too, use its destroy method.
(btw, it's a common convention in Javascript to use initial capital letter only for constructor functions, as clue that it should be used with the new operator, or be extended to create a derived constructor/class, so it's a bad idea to use Deliveries as a collection var name)