Double Clicking on row and getting respective row values - asp.net

I want to double click on any row to getting respective row values. It is an .Razor page.

First of all, I would create a component that will inherit from QuickGrid, so you can manage it easier in the future.
// CustomGrid.razor.cs
[CascadingTypeParameter( nameof(TGridItem) )]
public partial class CustomGrid<TGridItem> : QuickGrid<TGridItem>, IAsyncDisposable
{
[Inject]
private IJSRuntime JS { get; set; } // inject service in order to use JS interop
// sometimes it is mandatory to override base class parameters by your own
}
// CustomGrid.razor
#using Microsoft.AspNetCore.Components.QuickGrid // move it into the _Imports.razor
#typeparam TGridItem // QuickGrid is a generic-typed component
#inherits QuickGrid<TGridItem> // show inheritance
<div #ref="#_gridRef"> // HTML reference of current element
<QuickGrid TGridItem="TGridItem"
Items="#Items"
ItemsProvider="#ItemsProvider"
ChildContent="#ChildContent"
Class="#Class"
// more parameters... >
</QuickGrid>
</div>
Since there is no built-in functionality for adding your custom logic into QuickGrid, you will need to use some JavaScript interopability. Read about it more in docs here and here.
We need to declare some local variables in our CustomGrid.razor.cs:
private string? _rowValue; // specifies row value of currently double clicked row
private ElementReference _gridRef; // HTML element reference object that will be passed to JS function
private IJSObjectReference? _module; // JS module, a file that contains our JS functions
private DotNetObjectReference<CustomGrid<TGridItem>>? _objRef; // .NET object reference that will be passed to JS function in order to use its C# methods
And override some of the component lifecycle methods:
protected override void OnInitialized()
{
_objRef = DotNetObjectReference.Create( this ); // creates .NET object reference of current component instance
}
protected override async Task OnAfterRenderAsync( bool firstRender )
{
if( firstRender )
{
_module = await JS.InvokeAsync<IJSObjectReference>( "import", "./js/customGrid.js" ); // creates a reference of our JS module
if( _module is not null )
{
await _module.InvokeVoidAsync( "initialize", _gridRef, _objRef ); // calls our JS function and passes some arguments
}
}
}
Now, you need to create a JS module and a functions that will add desired logic for you on the first render of the CustomGrid component, like this:
// wwwroot/js/customGrid.js
export function initialize(customGrid, dotNetObj) {
if (customGrid) { // check if custom grid element exists
var rowValue;
const rows = customGrid.querySelectorAll('tbody > tr'); // get all rows except the header row
for (let i = 0; i < rows.length; i++) {
rows[i].addEventListener('dblclick', (e) => { // add event listener to current row in the loop
rowValue = e.path[1].innerText; // get innerText of current row in the loop
console.log(rowValue)
updateCurrentRowValue(rowValue, dotNetObj); // function that will return the current row value and refresh the UI
});
}
}
}
function updateCurrentRowValue(rowValue, dotNetObj) {
dotNetObj.invokeMethodAsync("UpdateCurrentRowValue", rowValue); // C# method
}
We're almost done here! If you would try to perform double click on the row, you would see an error in the console stating that CustomGrid does not contain a public method called UpdateCurrentRowValue. Let's add it like this:
[JSInvokable]
public void UpdateCurrentRowValue( string rowValue )
{
_rowValue = rowValue; // assign received rowValue from the JS function to our local _rowValue variable
StateHasChanged(); // force UI refresh
}
Now, all you need to do is to display your _rowValue:
// CustomGrid.razor
<div #ref="#_gridRef">
<QuickGrid TGridItem="TGridItem" . . . /> // collapsed for brevity
<p>Current Row Value: #_rowValue</p>
</div>
You will also need to Dispose your newly created objects of _module and _objRef using IAsyncDisposable.DisposeAsync method:
// CustomGrid.razor.cs
async ValueTask IAsyncDisposable.DisposeAsync()
{
if( _module is not null )
{
await _module.DisposeAsync();
}
_objRef?.Dispose();
}
Usage:
<CustomGrid Items="#people">
<PropertyColumn Property="#(p => p.PersonId)" Sortable="true" />
<PropertyColumn Property="#(p => p.Name)" Sortable="true" />
<PropertyColumn Property="#(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</CustomGrid>
That should work. If you will need any help -- don't hesitate to ask!
Remarks:
This is a basic implementation of your request. It doesn't support scenarios when there are more than 1 grid on the page. It might work, but will be buggy, I guess. For that, you will need to add some more code in JS and CustomGrid code-behind. I didn't add it because it would be too much code in one answer (quite a lot of code came out here anyway).
UPD-1:
Removed custom [Parameter]s to override QuickGrid's ones and added a comment.

Related

ASP.NET MVC: How to send the ids of the DOM Body elements on the client's browser TO the controller when navigating from the view

I am working on an ASP.NET MVC app (ASP.NET NOT ASP.NET Core).
When a View is rendered, the user can click on some buttons on the page to collapse or show divs associated with each button. The div changes its class depending on whether it is collapsed or shown. I am using bootstrap attributes for this, and it works fine.
Now I have a "Save" button on the page. When the user clicks on this button, I need to retrieve the ids and classes of the divs, and pass them TO the Controller (in an array/collection/dictionary whatever).
Is there a way/method in ASP.NET to send to the Controller the attributes (ids, classes, etc) of the DOM elements on the client's browser ?
Thanks
If you want to send some attributes of DOM to Controller, I have a way.
HTML:
<div id="demo-1" class="chosendiv other-className" data-code ="abc">Lorem Ipsum</div>
<div id="demo-2" class="chosendiv other-className" data-code ="xyz">Lorem Ipsum</div>
<div id="demo-3" class="other-className" data-code ="mnt">Lorem Ipsum</div>
<button id="btn-save" onclick="Save()">SAVE</button>
Javascript
<script>
function Save(){
var cds = document.getElementsByClassName('chosendiv');
var finder = [];
if(cds != null){
for(i = 0; i< cds.length; i++){
finder.push({
ID: cds[i].getAttribute('id'),
ClassName: cds[i].getAttribute('class'),
Code: cds[i].getAttribute('data-code')
})
}
}
//
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
//
$.ajax({
url: '/Home/YourAction',
type: 'GET', //<---- you can use POST method.
data:{
myDiv: JSON.stringify(finder)
},
success: function(response){
// Your code
}
})
}
</script>
Your Controller
public class HomeController: Controller
{
public HomeController(){}
[HttpGet]
public void YourAction(string myDiv)
{
//A lot of ways for converting string to Object, such as: creating new class for model, ...
// I use Dictionary Class
List<Dictionary<string, string>> temp = new List<Dictionary<string, string>>();
if(!string.IsNullOrEmpty(myDiv))
{
try
{
temp = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(myDiv);
}
catch { // Do something if it catches error. }
}
// Get a element (at index) from temp if temp.Count()>0
// var id = temp.ElementAt(index)["ID"];
// var className = temp.ElementAt(index)["ClassName"];
// var code = temp.ElementAt(index)["Code"];
//
//Your code
//
}
//......
}
It would be great if my answer could solve your problem.
Based on the answer provided by #Gia Khang
I made few changes in order to avoid the issue of the length of the URL exceeding the maximum limit.
Instead of adding the element's classes to an array using JS, I add them to a string :
function Save() {
var cds = document.getElementsByClassName('chosendiv');
// I use as string instead of an array
var finder = "";
if(cds != null){
for(i = 0; i< cds.length; i++){
finder = finder + "id=" + cds[i].getAttribute('id') + "class=" + cds[i].getAttribute('class') + "data-code=" +cds[i].getAttribute('data-code')
}
}
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
var myURL = "/{Controller}/{Action}"
$.ajax({
url: myURL,
type: "POST",
data: { ids:finder },
success: function (response) {
}
})
}
In the Controller Action I add a parameter named "ids" (this must be the same name as the identifier of the data object in the post request)and I extract the id, class, and data value from the ids string by a method in one of my Models classes (sorry I work with VB.NET not with C# and it will take me a lot of time to convert the code to C#. I use the Split method in VB to split the ids string several times: a first one by using "id=" as delimiter, then spiting each element in the resulting array by the second delimiter "class=", etc. I add the resulting elements to a collection)
The Controller Action looks like this:
public class HomeController: Controller
{
public HomeController(){}
[HttpPost]
public void YourAction(string ids)
{
Models.myClass.splitStringMethod(ids)
Return View()
}
}

How to skip displaying a content item in Orchard CMS?

I have a content part that provides a begin timestamp and end timestamp option. These 2 fields are used to define a period of time in which the content item should be displayed.
I now have difficulties to implement a skip approach whereas content items should not be displayed / skipped when the period of time does not span the current time.
Digging in the source code and trying to find an entry point for my approach resulted in the following content handler
public class SkipContentHandler : Orchard.ContentManagement.Handlers.ContentHandler
{
protected override void BuildDisplayShape(Orchard.ContentManagement.Handlers.BuildDisplayContext aContext)
{
if (...) // my condition to process only content shapes which need to be skipped
{
aContext.Shape = null; // return null shape to skip it
}
}
}
This works but there are several side effects
I had to alter the source code of BuildDisplayContext as the Shape is normally read only
List shape may displayed a wrong pager when it contains content items with my content part because the Count() call in ContainerPartDriver.Display() is executed before BuildDisplay()
calling the URL of a content item that is skipped results in an exception because View(null) is abigious
So, what would be the correct approach here or is there any module in existence that does the job? I couldn't find one.
This is a quite complex task. There are several steps needed to achieve a proper skipping of display items:
Create the part correctly
There are a few pitfalls here as when coming to the task of adding a part view one might utilize Orchards date time editor in connection with the DateTime properties. But this brings a heck of a lot of additional issues to the table but these don't really relate to the question.
If someone is interested in how to use Orchards date time editor then i can post this code too, but for now it would only blow up the code unnecessarly.
So here we go, the part class...
public class ValidityPart : Orchard.ContentManagement.ContentPart<ValidityPartRecord>
{
// public
public System.DateTime? ValidFromUtc
{
get { return Retrieve(r => r.ValidFromUtc); }
set { Store(r => r.ValidFromUtc, value); }
}
...
public System.DateTime? ValidTillUtc
{
get { return Retrieve(r => r.ValidTillUtc); }
set { Store(r => r.ValidTillUtc, value); }
}
...
public bool IsContentItemValid()
{
var lUtcNow = System.DateTime.UtcNow;
return (ValidFromUtc == null || ValidFromUtc.Value <= lUtcNow) && (ValidTillUtc == null || ValidTillUtc.Value >= lUtcNow);
}
...
}
...and the record class...
public class ValidityPartRecord : Orchard.ContentManagement.Records.ContentPartRecord
{
// valid from value as UTC to use Orchard convention (see CommonPart table) and to be compatible with projections
// (date/time tokens work with UTC values, see https://github.com/OrchardCMS/Orchard/issues/6963 for a related issue)
public virtual System.DateTime? ValidFromUtc { get; set; }
// valid from value as UTC to use Orchard convention (see CommonPart table) and to be compatible with projections
// (date/time tokens work with UTC values, see https://github.com/OrchardCMS/Orchard/issues/6963 for a related issue)
public virtual System.DateTime? ValidTillUtc { get; set; }
}
Create a customized content query class
public class MyContentQuery : Orchard.ContentManagement.DefaultContentQuery
{
// public
public ContentQuery(Orchard.ContentManagement.IContentManager aContentManager,
Orchard.Data.ITransactionManager aTransactionManager,
Orchard.Caching.ICacheManager aCacheManager,
Orchard.Caching.ISignals aSignals,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentTypeRecord> aContentTypeRepository,
Orchard.IWorkContextAccessor aWorkContextAccessor)
: base(aContentManager, aTransactionManager, aCacheManager, aSignals, aContentTypeRepository)
{
mWorkContextAccessor = aWorkContextAccessor;
}
protected override void BeforeExecuteQuery(NHibernate.ICriteria aContentItemVersionCriteria)
{
base.BeforeExecuteQuery(aContentItemVersionCriteria);
// note:
// this method will be called each time a query for multiple items is going to be executed (e.g. content items of a container, layers, menus),
// this gives us the chance to add a validity criteria
var lWorkContext = mWorkContextAccessor.GetContext();
// exclude admin as content items should still be displayed / accessible when invalid as validity needs to be editable
if (lWorkContext == null || !Orchard.UI.Admin.AdminFilter.IsApplied(lWorkContext.HttpContext.Request.RequestContext))
{
var lUtcNow = System.DateTime.UtcNow;
// left outer join of ValidityPartRecord table as part is optional (not present on all content types)
var ValidityPartRecordCriteria = aContentItemVersionCriteria.CreateCriteria(
"ContentItemRecord.ValidityPartRecord", // string adopted from foreach loops in Orchard.ContentManagement.DefaultContentQuery.WithQueryHints()
NHibernate.SqlCommand.JoinType.LeftOuterJoin
);
// add validity criterion
ValidityPartRecordCriteria.Add(
NHibernate.Criterion.Restrictions.And(
NHibernate.Criterion.Restrictions.Or(
NHibernate.Criterion.Restrictions.IsNull("ValidFromUtc"),
NHibernate.Criterion.Restrictions.Le("ValidFromUtc", lUtcNow)
),
NHibernate.Criterion.Restrictions.Or(
NHibernate.Criterion.Restrictions.IsNull("ValidTillUtc"),
NHibernate.Criterion.Restrictions.Ge("ValidTillUtc", lUtcNow)
)
)
);
}
}
// private
Orchard.IWorkContextAccessor mWorkContextAccessor;
}
This essentially adds a left join of the validity part fields to the SQL query (content query) and extends the WHERE statement with the validity condition.
Please note that this step is only possible with the solution described the following issue: https://github.com/OrchardCMS/Orchard/issues/6978
Register the content query class
public class ContentModule : Autofac.Module
{
protected override void Load(Autofac.ContainerBuilder aBuilder)
{
aBuilder.RegisterType<MyContentQuery>().As<Orchard.ContentManagement.IContentQuery>().InstancePerDependency();
}
}
Create a customized content manager
public class ContentManager : Orchard.ContentManagement.DefaultContentManager
{
// public
public ContentManager(
Autofac.IComponentContext aContext,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentTypeRecord> aContentTypeRepository,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentItemRecord> aContentItemRepository,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentItemVersionRecord> aContentItemVersionRepository,
Orchard.ContentManagement.MetaData.IContentDefinitionManager aContentDefinitionManager,
Orchard.Caching.ICacheManager aCacheManager,
System.Func<Orchard.ContentManagement.IContentManagerSession> aContentManagerSession,
System.Lazy<Orchard.ContentManagement.IContentDisplay> aContentDisplay,
System.Lazy<Orchard.Data.ITransactionManager> aTransactionManager,
System.Lazy<System.Collections.Generic.IEnumerable<Orchard.ContentManagement.Handlers.IContentHandler>> aHandlers,
System.Lazy<System.Collections.Generic.IEnumerable<Orchard.ContentManagement.IIdentityResolverSelector>> aIdentityResolverSelectors,
System.Lazy<System.Collections.Generic.IEnumerable<Orchard.Data.Providers.ISqlStatementProvider>> aSqlStatementProviders,
Orchard.Environment.Configuration.ShellSettings aShellSettings,
Orchard.Caching.ISignals aSignals,
Orchard.IWorkContextAccessor aWorkContextAccessor)
: base(aContext, aContentTypeRepository, aContentItemRepository, aContentItemVersionRepository, aContentDefinitionManager, aCacheManager, aContentManagerSession,
aContentDisplay, aTransactionManager, aHandlers, aIdentityResolverSelectors, aSqlStatementProviders, aShellSettings, aSignals)
{
mWorkContextAccessor = aWorkContextAccessor;
}
public override ContentItem Get(int aId, Orchard.ContentManagement.VersionOptions aOptions, Orchard.ContentManagement.QueryHints aHints)
{
var lResult = base.Get(aId, aOptions, aHints);
if (lResult != null)
{
// note:
// the validity check is done here (after the query has been executed!) as changing base.GetManyImplementation() to
// apply the validity critera directly to the query (like in ContentQuery) will not work due to a second attempt to retrieve the
// content item from IRepository<> (see base.GetManyImplementation(), comment "check in memory") when the query
// returns no data (and the query should not return data when the validity critera is false)
//
// http://stackoverflow.com/q/37841249/3936440
var lWorkContext = mWorkContextAccessor.GetContext();
// exclude admin as content items should still be displayed / accessible when invalid as validity needs to be editable
if (lWorkContext == null || !Orchard.UI.Admin.AdminFilter.IsApplied(lWorkContext.HttpContext.Request.RequestContext))
{
var lValidityPart = lResult.As<ValidityPart>();
if (lValidityPart != null)
{
if (lValidityPart.IsContentItemValid())
{
// content item is valid
}
else
{
// content item is not valid, return null (adopted from base.Get())
lResult = null;
}
}
}
}
return lResult;
}
// private
Orchard.IWorkContextAccessor mWorkContextAccessor;
}
Steps 2-4 are needed when having content items whereas the content type has a Container and Containable part or even content items which are processed / displayed separately. Here you normally cannot customize the content query that is executed behind the scenes.
Steps 2-4 are not needed if you use the Projection module. But again, this brings a few other issues to the table as reported in this issue: https://github.com/OrchardCMS/Orchard/issues/6979

Dart Language: observable

I have two table rows at an HTML file. When the first row gets clicked, it changes its styling via classes.add("active_style"). If the second row gets clicked, I would like to clear the first row styling.
I know that I can just write...
querySelector("#first_row_div").classes.clear();
... in order to clear the first row class (and then resetting its style), but in a bigger code I think that observable would be the best fit.
I don't know if observable works for this. But, if it does, how can I do that?
EDIT/UPDATE: I think that the right question is "is there any way to run a function when a variable gets changed?".
Thanks for the help!
You can make a getter/setter for a field and run your function in the setter.
class MyClass {
String _cssClass;
String get cssClass => _cssClass;
set cssClass(String newClass) {
_cssClass = newClass;
updateDom();
}
void updateDom() {
// do important work here
}
}
You can use a model class that extends Observable.
Here you have to call dirtyCheck() to make Observable check for changes and notify listeners.
Dart also offers the ChangeNotifier mixin. Here you don't need to call any method for dirty-checking. When changes are made listeners are invoked.
A simple example I wrote a while ago while examining the functionality
import 'package:observe/observe.dart';
class Notifiable extends Object with ChangeNotifier {
String _input = '';
#reflectable
get input => _input;
#reflectable
set input(val) {
_input = notifyPropertyChange(#input, _input, val + " new");
}
Notifiable() {
this.changes.listen((List<ChangeRecord> record) => record.forEach(print));
}
}
class MyObservable extends Observable {
#observable
String counter = '';
MyObservable() {
this.changes.listen((List<ChangeRecord> record) => record.forEach(print));
}
}
void main() {
var x = new MyObservable();
x.counter = "hallo";
Observable.dirtyCheck();
Notifiable notifiable = new Notifiable();
notifiable.input = 'xxx';
notifiable.input = 'yyy';
}

Adding drawing objects to a collection in Flex 4

I'm trying to make annotations on an image.
I want to store all these annotations into a collection so that I can later remove or edit particular note(annotation).
I'm creating a UIComponent instance for example markUp and doing
markUp.graphics.lineStyle(2, drawColor);
markUp.graphics.moveTo(x1, y1);
markUp.graphics.lineTo(x2, y2);
I want to create a UIComponent instance every time whenever user makes a new annotation on my image.
How to generate dynamic instance and how to save them to a collection. Any help is appreciated.
From what I understand after a chat with Kishor:
Users may mark specific areas by mouse drag and add a comment accordingly. Markup and comment are stored in the database.
There is only one image enlarged at a time, and this image should display all markups. Clicking on a markup will raise a popup showing the associated comment.
Apparently drawing the markup is rather expensive, so once drawn a particular markup should be reused when the user switchs between images. The question is now:
How to reuse markups displayed on an image?
Answer:
Use an Object or Dictionary where you create one entry per image and the entry points to an Array of markups. You may create a convenient class to encapsulate the functionality of this map:
package {
import flash.display.DisplayObject;
public class Markups {
private var _map : Object;
public function Markups() {
_map = new Object();
}
public function addMarkup(imageId : String, markup : DisplayObject) : void {
if (!_map[imageId]) _map[imageId] = new Array();
(_map[imageId] as Array).push(markup);
}
public function hasMarkups(imageId : String) : Boolean {
return _map[imageId] !== undefined;
}
public function getMarkups(imageId : String) : Array {
return _map[imageId];
}
public function removeMarkup(imageId : String, markup : DisplayObject) : void {
var markups : Array = _map[imageId];
var index : int = markups.indexOf(markup);
markups.splice(index, 1);
if (!markups.length) delete _map[imageId];
}
}
}
Store this map at a place that is accessible from your image container.
var markupsPerImage : Markups = new Markups();
Add or remove a markup like this:
markupsPerImage.addMarkup(myImage.id, markupView);
markupsPerImage.removeMarkup(myImage.id, markupView);
Whenever the user changes the current image you 1. cleanup all markups of the last and 2. add all markups stored in the map.
In the image container:
public function setImage(myImage : MyImage) : void {
// remove markups
while (markupContainer.numChildren) {
markupContainer.removeChildAt(0);
}
// add markups
var markups : Array = markupsPerImage.getMarkups(myImage.id);
var markup : MarkupView;
foreach (markups as markup) {
markupContainer.addChild(markup);
}
}
Notes:
The example code assumes you have a markup container that hosts all markups. This container should have a reference to the markup map.

Get old text from change event?

Can I somehow find out what was the change in the textfield? I would want to compare the old text with the new text ... the problem is, that I have multiple textAreas in a tab-editor, and all the textAreas are watched by one eventListener. I want to get a value calculated by the next formula:
globalChangeCount += thisTextArea.currentCharacterCount - thisTextArea.oldtCharacterCount
where the globalChangeCount is a value modified by all changes in any of the textAreas.
I am searching for these values through the event variable, but can't seam to find the old text of the textArea.
This may or may not be what you're looking to do:
package
{
import mx.controls.TextArea;
public class CountingTextArea extends TextArea
{
public var staleText : String = "";
[Bindable("textChanged")]
[NonCommittingChangeEvent("change")]
public function get charDiff() : int
{
var diff : int = staleText.length - text.length;
staleText = text;
return diff;
}
public function CountingTextArea()
{
super();
}
}
}
I made it so that you can use it as a source for binding. Instead of subscribing to the event on each TextArea, you can use:
function addWatchers():void
{
ChangeWatcher.watch(countingTextArea1, ["charDiff"], charDiffChangeHandler );
...
ChangeWatcher.watch(countingTextArea5, ["charDiff"], charDiffChangeHandler );
}
With the event handler somewhere too:
function charDiffChangeHandler( event : PropertyChangeEvent ) : void
{
trace(event.currentTarget.charDiff);
// or
trace(event.newValue);
}
You can use event.currentTarget to get a reference to the TextArea that fired the event, and use the focusIn event to execute a function to populate a variable with the old text value.
Maybe you should just subclass the TextArea and create an oldText field variable you update internally after all the external listeners have been notified.

Resources