Symfony 4 x-editable - update - symfony

My mission is to use the x-editable library for bootstrap in Symfony 4.
After clicking on the field in the table appears "input" and the value from the field inside. I have a problem to make an update to the database. In the code below I am doing an update to the database with a fixed value of "Koralik". I would like the update with the value from "input" to go to the base.
<table id="tab1" class="table table-bordered table-striped table-hover">
<thead style="color:blue;">
<tr><th>ImiÄ™</th><th>Nazwisko</th></tr>
</thead>
<tbody id="myTable">
{%for kierowca_one in kierowca%}
<tr class="text-success">
<td>{{kierowca_one.Imie}}</td>
<td><span class="myElement" data-type="text" data-pk="
{{kierowca_one.id}}" data-url="{{path('app_update',
{id:kierowca_one.id})}}">{{kierowca_one.Nazwisko}}</span> </td>
</tr>
{%endfor%}
</tboody>
</table>
$('.myElement').editable({
type: "POST",
emptytext: 'Brak',
showbuttons: false,
mode: 'inline',
//dataType: 'json',
validate: function(value){
if($.trim(value) == '')
{
return 'This field is required';
}
}
});
/**
* #Route("/update/{id}", name="app_update", methods={"GET"})
* #IsGranted("ROLE_USER")
*/
public function updateMethod(EntityManagerInterface $em, $id)
{
$repository=$em->getRepository(Kierowcy::class);
$kierowca=$repository->find($id);
$kierowca->setNazwisko('Kowalik');
$em->persist($kierowca);
$em->flush();
return new Response();
}

here is my approach.
js file
$('#s').editable({
selector: '.js-editable',
url: urlUpdateTopic,
params:function(params){
params.pk = $(this).closest('tr').data('id');
return params;
},
success: function(value, response) {
if(response){
$(this).html(value);
}
},
error: function(response) {
if(response.status === 500) {
console.log('Service unavailable. Please try later.');
} else {
console.log(response.responseText);
}
},
type: 'text',
pk: 1,
mode: 'inline',
showbuttons: false,
toggle: 'dblclick',
onblur: "submit",
clear: false,
emptytext: ''
});
here is controller
public function updateTopicAction(Request $request)
{
$id = $request->request->get('pk');
$value = $request->request->get('value');
}
also you can see a good example here Submitting data via Ajax in X-Editable

Related

Change User Role in Admin Panel

I'm creating an admin panel where I am able to change the user roles via a select control while using alanning:roles. The issue I am having is I am unable to get the user that is associated with each select control. I referenced this tutorial on creating an admin panel with Meteor, but when I call the method to changeRoles, the console returns this error:
Original Issue: Solved
Error: Missing 'users' param
I20190218-14:59:27.319(-6)? at Object._updateUserRoles (packages/alanning_roles.js:684:23)
I20190218-14:59:27.319(-6)? at Object.setUserRoles (packages/alanning_roles.js:250:11)
I20190218-14:59:27.320(-6)? at MethodInvocation.changeRole (server/main.js:88:13)`
Changed code from:
Template.userHome.events({
'click #confirmChanges': function (event) {
let currentRole = $(event.target).find('option:selected').val();
let userId = $(event.target.id).val();
console.log(userId);
if (window.confirm("Change User Roles?")) {
Meteor.call("changeRole", {
role: currentRole,
user: userId
})
window.location.reload();
}
}
})
To this (change in how I set userId value):
Template.userHome.events({
'change [name="userRole"]': function (event) {
let currentRole = $(event.target).find('option:selected').val();
let userId = $(event.target.id);
console.log(currentRole);
if (window.confirm("Change User Roles?")) {
Meteor.call("changeRole", {
role: currentRole,
user: userId
})
window.location.reload();
}
}
})
Now, Roles.setUserRoles() is not working yet parameters have values
console.log(options.role) returns the correct value and
console.log(options.user) returns:
I20190219-20:37:37.527(-6)? { length: 0,
I20190219-20:37:37.528(-6)? prevObject:
I20190219-20:37:37.529(-6)? { length: 0,
I20190219-20:37:37.529(-6)? prevObject:
I20190219-20:37:37.529(-6)? { length: 0,
I20190219-20:37:37.530(-6)? prevObject: [Object],
I20190219-20:37:37.530(-6)? context: [Object],
I20190219-20:37:37.531(-6)? selector: '3FzfDhZWcGFg6ggTE' },
I20190219-20:37:37.531(-6)? context: { location: [Object] } },
I20190219-20:37:37.532(-6)? context:
I20190219-20:37:37.532(-6)? { location:
I20190219-20:37:37.532(-6)? { href: 'http://localhost:3000/userHome',
I20190219-20:37:37.533(-6)? ancestorOrigins: {},
I20190219-20:37:37.533(-6)? origin: 'http://localhost:3000',
I20190219-20:37:37.534(-6)? protocol: 'http:',
I20190219-20:37:37.534(-6)? host: 'localhost:3000',
I20190219-20:37:37.534(-6)? hostname: 'localhost',
I20190219-20:37:37.534(-6)? port: '3000',
I20190219-20:37:37.535(-6)? pathname: '/userHome',
I20190219-20:37:37.535(-6)? search: '',
I20190219-20:37:37.536(-6)? hash: '' } } }
Client Code:
Template.userHome.events({
'change [name="userRole"]': function (event) {
let currentRole = $(event.target).find('option:selected').val();
let userId = $(event.target.id);
console.log(currentRole);
if (window.confirm("Change User Roles?")) {
Meteor.call("changeRole", {
role: currentRole,
user: userId
})
window.location.reload();
}
}
})
Server Code:
Meteor.methods({
changeRole( options ) {
console.log("Change User is Being Called");
try {
Roles.setUserRoles( options.user, [ options.role ] );
console.log(options.role)
} catch( exception ) {
console.log(exception);
}
}
});
UserHome Template
<div class="nav">
<p class="welcomeUser">Welcome, {{#with userInfo}}{{profile.firstname}} {{profile.lastname}}{{/with}}</p>
<button id="logout" class="universalButton">Log Out</button>
</div>
<div class="pageContent">
{{#if userIsAdmin}}
<div class="adminPanel">
<table id="userTable">
<caption>Admin Panel</caption>
<tr>
<th class="usertableHead">Username</th>
<th class="usertableHead">Role</th>
</tr>
{{#each users}}
<tr>
<td class="usertableData">{{username}}</td>
<td class="usertableData">
<div class="styled-select purple rounded">
<select id={{_id}} name="userRole">
<option value={{roles}}>{{roles}}</option>
<option value="Teacher">Teacher</option>
</select>
</div>
</td>
</tr>
{{/each}}
</table>
<button id="confirmChanges" class="universalButton">Confirm Changes</button>
</div>
{{/if}}
</div>
console.log(options.user) should print the _id of the user, not a jQuery object. In your change [name="userRole"] event, replace let userId = $(event.target.id); with let userId = event.target.id;
The better way of assigning and retrieving values is as below:
HTML:
...
<select data-id={{_id}} name="userRole">
<option value={{roles}}>{{roles}}</option>
<option value="Teacher">Teacher</option>
</select>
...
JS:
Template.userHome.events({
'change [name="userRole"]': function (event) {
let userId = event.target.dataset.id;
let currentRole = event.target.value;
console.log(userId, currentRole);
...
}
})

DataTable Server-side processing ajax error

I have a form/page in which do a search. That action returns datatabel type (this is a requirement) then the results I have to show in jquery datatable and the processing has to be done server-side.
I read the documentation and got some examples but I can't get it work.
$(document).ready(function () {
var table = $("#searchResultTable").DataTable({
"processing": true,
"serverSide": true,
"ajax": {
"url": "Report/CustomServerSideAction",
"type": "POST"
},
"columns":[
{"data": "ID"},
{"data": "Name"},
{"data": "Active"},
{"data": "OrderBy"},
{"data": "StoredProcedure"},
{"data": "SSRSFileName"},
{"data": "SSESStoredProcedure"}
]
//buttons: [
// "csv", "excel", "pdf", "print"
//]
});
});
#using System.Data
#model System.Data.DataTable
<div class="col-sm-12">
<div class="card-box table-responsive">
<table id="searchResultTable" class="testClass table table-striped table-colored table-teal">
<thead>
<tr>
#foreach (DataColumn col in Model.Columns)
{
<th>#col.ColumnName</th>
}
</tr>
</thead>
<tbody>
#foreach (DataRow item in Model.Rows)
{
<tr>
#foreach (DataColumn col in Model.Columns)
{
switch (col.DataType.ToString())
{
case "System.Decimal":
<td class="pull-right">#Convert.ToDecimal(item[col].ToString())</td>
break;
default:
<td>#item[col].ToString()</td>
break;
}
}
</tr>
}
</tbody>
</table>
</div>
</div>
[HttpPost]
public ActionResult CustomServerSideAction()
{
var table = Models.SearchResult.Results;
return Json(new { draw = 100, totalRecors = 30, dislayRecord = 10, data = table }, JsonRequestBehavior.AllowGet);
}
public static class SearchResult
{
public static DataTable Results { get; set; }
}
This gives an error
DataTables warning: table id=searchResultTable - Ajax error. For more
information about this error, please see http://datatables.net/tn/7

Rocket Chat Filter changes with result set

I am updating the rocket chat app to have a departments filter on the department list page. I am running into an issue where my filter seems to be tied to the same collection as the result set. So when I update the filter all the other filter options are removed. I'm not sure the best way to make it so the filter only impacts the result list and not both.
Before:
After:
HTML
<template name="livechatDepartments">
{{#requiresPermission 'view-livechat-manager'}}
<fieldset>
<form class="form-inline" method="post">
<div class="form-group">
<label for="department">{{_ "Department"}}</label>
<select name="department">
<option value=""></option>
{{#each departmentsDDL}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
</div>
<div class="form-group">
<label for="agent">{{_ "Served_By"}}</label>
<select name="agent">
<option value=""></option>
{{#each agents}}
<option value="{{_id}}">{{username}}</option>
{{/each}}
</select>
</div>
<button class="button">{{_ "Filter"}}</button>
</form>
</fieldset>
<div class="list">
<table>
<thead>
<tr>
<th width="20%">{{_ "Name"}}</th>
<th width="30%">{{_ "Description"}}</th>
<th width="10%">{{_ "Num_Agents"}}</th>
<th width="10%">{{_ "Num_Available_Agents"}}</th>
<th width="20%">{{_ "Enabled"}}</th>
<th width="20%">{{_ "Show_on_registration_page"}}</th>
<th>{{_ "Delete"}}</th>
</tr>
</thead>
<tbody>
{{#each departments}}
<tr class="department-info row-link" data-id="{{_id}}">
<td>{{name}}</td>
<td>{{description}}</td>
<td>{{numAgents}}</td>
<!--<td>{{}}</td>-->
<td>{{#if enabled}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td>
<td>{{#if showOnRegistration}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td>
<td><i class="icon-trash"></i></td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<div class="text-center">
<button class="button load-more">{{_ "Load_more"}}</button>
</div>
{{_ "New_Department"}}
{{/requiresPermission}}
JS:
Template.livechatDepartments.helpers({
departmentsDDL() {
return LivechatDepartment.find({}, { sort: { name: -1 } });
},
departments() {
return LivechatDepartment.find({}, { sort: { name: -1 } });
},
agents() {
return AgentUsers.find({}, { sort: { name: 1 } });
}
});
Template.livechatDepartments.events({
'click .remove-department' (e /*, instance*/ ) {
e.preventDefault();
e.stopPropagation();
swal({
title: t('Are_you_sure'),
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#DD6B55',
confirmButtonText: t('Yes'),
cancelButtonText: t('Cancel'),
closeOnConfirm: false,
html: false
}, () => {
Meteor.call('livechat:removeDepartment', this._id, function(error /*, result*/ ) {
if (error) { return handleError(error); }
swal({
title: t('Removed'),
text: t('Department_removed'),
type: 'success',
timer: 1000,
showConfirmButton: false
});
});
});
},
'click .department-info' (e /*, instance*/ ) {
e.preventDefault();
FlowRouter.go('livechat-department-edit', { _id: this._id });
},
'submit form' (e, instance) {
e.preventDefault();
const filter = {};
$(':input', event.currentTarget)
.each(function() {
if (this.name) {
filter[this.name] = $(this)
.val();
}
});
instance.filter.set(filter);
instance.limit.set(20);
}
});
Template.livechatDepartments.onCreated(function() {
this.limit = new ReactiveVar(20);
this.filter = new ReactiveVar({});
this.subscribe('livechat:agents');
this.autorun(() => {
this.subscribe('livechat:departments', this.filter.get(), 0, this.limit.get());
});
});
Meteor Method:
Meteor.publish("livechat:departments", function(filter = {}, offset = 0, limit = 20) {
if (!this.userId) {
return this.error(
new Meteor.Error("error-not-authorized", "Not authorized", {
publish: "livechat:agents"
})
);
}
if (!RocketChat.authz.hasPermission(this.userId, "view-l-room")) {
return this.error(
new Meteor.Error("error-not-authorized", "Not authorized", {
publish: "livechat:agents"
})
);
}
check(filter, {
agent: Match.Maybe(String), // agent _id who is serving
department: Match.Maybe(String)
});
const query = {};
if (filter.agent) {
const DepartmentFilter = [];
RocketChat.models.LivechatDepartmentAgents
.find({
agentId: filter.agent
})
.forEach(department => {
DepartmentFilter.push(department);
});
var depts = DepartmentFilter.map(function(dep) {
return dep.departmentId;
});
As you stated in the question, your filter is tied to the same collection as your results set. So, how can you fix this?
Solution 1 - Easy, and if data in livechat:departments collection is not too large, probably the best:
Revert back your subscription code to fetch all data (not filtered), and filter in the departments helper function
// in Template.livechatDepartments.onCreated
this.subscribe('livechat:departments');
// in Template.livechatDepartments.helpers
departments() {
const departmentFilter = Template.instance().filter.get().department;
if (departmentFilter){
return LivechatDepartment.find({name: departmentFilter }, { sort: { name: -1 } });
}
else {
return LivechatDepartment.find({}, { sort: { name: -1 } });
}
}
Solution 2 - Keep departments helper with filter from Solution 1 ,
but now subscribe twice to livechat:departments
You can reuse the current publish for the filtered list of departments (add back your filtered subscription), and create a new pub/sub channel that publishes all the departments, but only needs to send the name + _id fields used to populate select options.

Using Handlebars for conditional CSS

In the snippet below I'd expect row a to have the class new-entry and row c to have the class moving-up but neither does.
I'm certain this is a silly mistake but I can't see it
Handlebars.registerHelper("newEntry", function() {
return this.newEntry ? 'class="new-entry"' : '';
});
Handlebars.registerHelper("movingUp", function() {
return this.movingUp ? 'class="moving-up"' : '';
});
var source = document.getElementById('leaderboard-template').innerHTML;
var template = Handlebars.compile(source);
var outlet = document.getElementById('outlet');
outlet.innerHTML = template({leaders: [
{name: 'a', signature_count: 10, newEntry: true},
{name: 'b', signature_count: 8},
{name: 'c', signature_count: 6, movingUp: false},
]});
.new-entry {
background-color:red;
}
.moving-up {
color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js"></script>
<script id="leaderboard-template" type="text/x-handlebars-template">
<table>
<thead>
<th>Constituency</th>
<th>Votes</th>
</thead>
<tbody>
{{#leaders}}
<tr {{newEntry}}>
<td>{{name}}</td>
<td><span {{movingUp}}>{{signature_count}}</span></td>
</tr>
{{/leaders}}
</tbody>
</table>
</script>
<div id="outlet"></div>
Handlebar converts the return value of a helper to an HTML escaped string. Use Handlebars.SafeString like this if you don't want that:
Handlebars.registerHelper("newEntry", function() {
return new Handlebars.SafeString( this.newEntry ? 'class="new-entry"' : '' );
});
Handlebars.registerHelper("movingUp", function() {
return new Handlebars.SafeString( this.movingUp ? 'class="moving-up"' : '' );
});

Dynamically add/remove table rows in a wizard

I'm currently using this Wizard in my application.
In one of my steps, I need to add and remove items from a table. Add functions works fine. But I can't make the Delete function work. I've searched SO and other resources online, can't find a solution.
This is what I've so far:
Wizard Step 2 Table:
<table id="LibList" class="table table-responsive table-striped">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Types)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Teams)
</th>
<th>
<label>Delete</label>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Step3.Liabilities) {
Html.RenderPartial("_LibListItem", item);
}
</tbody>
</table>
Partial View:
<tr>
<td>
#Html.DropDownListFor(model => model.Type, Model.Types, new { #class = "selectpicker form-control", id = string.Format("{0}_{1}", "TYPE", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Name, new { #class = "form-control", id = string.Format("{0}_{1}", "NAME", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Teams, new { #class = "form-control", #type = "currency", id = string.Format("{0}_{1}", "TERMS", Model.ID) })
</td>
<td>
#Ajax.ActionLink("Delete", "Delete", "QuestionWizard", new { id = Model.ID },
new AjaxOptions() {
HttpMethod = "Delete",
Confirm = "Are you sure you want to delete this record?",
OnComplete = "function() { $(this).parent().parent().remove() }"
},
new { #class = "btn btn-primary" })
</td>
Add and Delete Action:
public ActionResult AddRow() {
LiabilityModel lib = new LiabilityModel();
try {
if (LiabilitySessionState.Count == 0) {
lib.ID = 1;
} else {
lib.ID = LiabilitySessionState.LastOrDefault().ID + 1;
}
LiabilitySessionState.Add(lib);
return PartialView("_LibListItem", lib);
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
return RedirectToAction("Index", "Error");
}
}
public void Delete(int Id) {
try {
if (LiabilitySessionState.Count > 0) {
LiabilitySessionState.RemoveAll(item => item.ID == Id);
}
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
RedirectToAction("Index", "Error");
}
}
public List<LiabilityModel> LiabilitySessionState {
get {
if (Session["LiabilityModel"] == null) {
return LiabilitySessionState = new List<LiabilityModel>();
} else {
return Session["LiabilityModel"] as List<LiabilityModel>;
}
}
set {
Session["LiabilityModel"] = value;
}
}
Script:
<script type="text/javascript">
$(document).ready(function () {
$('.add-button').click(function () {
var action = "/QuestionWizard/AddRow";
$.ajax({
url: action,
cache: false,
success: function (result) {
$("#LibList tbody").append($(result));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
}
});
})
$(".remove-button").click(function () {
$(this).parents().parent().remove();
return false;
});
});
Right now, the Delete Action returns a blank page, because I'm not returning a view. But, I'm not sure what view to return.
Please help!
Thanks in advance.
Firstly your Delete() method is changing data so it needs to be a POST, not a GET (which #Ajax.ActionLink() is). Next, your 'delete link' does not have class="remove-button" so $(".remove-button").click(function () { wont do anything. Since you already using jquery ajax methods to add items (refer notes below), there seems no point using the Ajax helpers anyway (your just adding extra overhead by loading the jquery.unobtrusive-ajax.js file).
Change the last <td> element of the partial to
<td>
<button type="button" class="remove-button" data-id=#Model.ID>Delete</button>
</td>
Then in the main view, use the following script (note you need event delegation so you can remove dynamically added items)
var url = '#Url.Action("Delete", "YourControllerName")';
$('#LibList').on('click', .remove-button, function() {
var row = $(this).closest('tr');
var id = $(this).data('ID');
if (id) { // assumes property ID is nullable, otherwise may need to test if id != 0
$.post(url, {ID: id }, function(response) {
if(response) {
row.remove();
} else {
// Oops - display message?
}
});
} else {
// its a new row so it has not been saved yet - just delete the row
row.remove();
}
});
and the controller method should look like
[HttpPost]
public JsonResult Delete(int ID)
{
// delete the item
return Json(true); // or if an exception occurred, return(null) to indicate failure
}
Side notes:
Both your AddRow() and Delete() methods include a return
RedirectToAction() statement. You are making ajax calls which stay
on the same page so RedirectToAction() is pointless (it will never
be executed)
If you believe the "Add functions works fine", then you must have
some unnecessary hacks in order to make this work (your generating
duplicate name attributes for the controls (without indexers)
which will not bind to your collection of Liabilities). You need
to generate existing items in a for loop and include a hidden
input for an Index property, and use a client side template to
generate new items; or you need to use the BeginCollectionItem
helper if using a partial view. This
answer
shows how to use both techniques.
You don't appear (and you should not need) to access the id
attributes of the controls in your table. Instead of using id = string.Format("{0}_{1}", "NAME", Model.ID), just use id="" which
will render the element without and id attribute.

Resources