Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Tuesday, March 10, 2020

Front-End Challenge from Colt Steele: Stepper Form

In the past weeks I reviewed a bit my front-end (html-css-javascript) lessons, and also found an interesting challenge lately. It is a vanilla js challenge by Colt Steele, published on Youtube:


My solution to it:
https://github.com/zoltanhalasz/StepperForm

Working link(live project):
https://codepen.io/zoltanhalasz/project/editor/ApPwKj

Explanation:
-I am using a structure with div's with classes and id's that can be targeted by Javascript DOM manipulation.
- the script uses lots of functions, and the getElementById used to target the classes/ids
- use hide/show for the stepper form via Javascript (manipulating DOM styles)

Recommended reading:
- Javascript DOM: https://www.w3schools.com/js/js_htmldom.asp
- CSS/Html: https://www.w3schools.com/html/default.asp

Tuesday, March 3, 2020

Full Stack Asp.Net Core App (Bootcamp Project) - Part 4 - The Front-End

This is the continuation of thrid part of the series.


As mentioned, all html, css and javascript is written manually (without frameworks) in the application. The html and css will not be explained here, instead, we will focus to describe in more detail the front-end Javascript (DOM manipulation) from the Notes html page, which is the main area of the whole application.

This html / javascript was learned as a bootcamp, it's not perfect, just an attempt to show the capabilities of JS in the browser, together with AJAX (Fetch API).

I recommend that the following material is reviewed before reading further:
1. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
2. https://mydev-journey.blogspot.com/2020/02/expense-tracker-from-traversy-media.html
3. https://mydev-journey.blogspot.com/2020/02/full-stack-mini-todo-app-with.html
4. https://mydev-journey.blogspot.com/2020/02/mybooklist-another-traversy-tutorial.html

Link to Github Repo of notes page.

The below will be a succinct description of the existing html/javascript code of the notes page.

a. ) The whole start of the action is bootstrapped at the bottom of the page:

   <script>
        window.onload = () => {
            //initialization of functions and showing the notes
            getNotesForUser(myLoginUser, "Desc","(empty)");
            showColorPicker();
            addClickListeners();         
        }
    </script>
b.) there are two variables in the pagemodel, and these will be taken to the front-end and injected in the javascript code:
- user email - to be displayed at the top of the page:  Welcome @Model.LoginUserEmail!
- user id, which is crucial in the operation of notes:  var myLoginUser = @Model.LoginUserID;

c.) in the top of the page, will be  a form for the filtering and ordering of notes (div id="header")


d.) in the main of the html page, we will have a form id="new-note" that will save the new notes.

The onsubmit function is written below, saves the new notes (plus images, colors).

e.) below the main form we will have the list of notes displayed using getNotesForUser function.
This has a fetch with promise, and in the "then", we retrieve the images for the user. Both on the retrieved notes and images array, the insertNotesList(displayNotesList(notes, imgList))  is called which draws the list of notes and pictures.

f.) displayNote function will take the note with id x, and its pictures, and draws the container for the note, inserting pictures.
Note: if there are multiple pictures (allowed to update), the user is allowed to navigate between them, using clicks on the pictures. To see how it's done, please study getCarousel function.

g.) deleting a note: using the trash sign, we have a click event handler createDeleteNote on it. This will create a popup, and if "yes" is chosen, the removeNote function will delete the note from the Dom and back-end.


h.) editing a note: on the pencil icon of a note, createPopin function is invoked upon click, which will display a small form that will give the possibility to change title, content, delete pictures (all), add picture (only 1), and save the note.

i.) change the color of a note: also in the form (new note) and within each individual note: see getColorPickerHtml function. It will display a list of colors, which will change the background color of selected note, and save the color on the back-end for future use.

j.) pin-unpin: this functionality will pin a note, that means it will be shown first, irrespective of sorting/ordering of notes. The pinning will be done also on the back-end, marking the pinned property of a note also in the database. See function pinNote, and also the back-end API with the route: /api/Notes/pinnote

k.) search function - on key press in the search box, the page will send the query to the back-end using the fetch api in getNotesForUser

            getNotesForUser(myLoginUser, ordering, searchterm);

The web API behind this fetch, is able to sort the result using ordering, and find only those notes which have searchterm in title or content. The default searchterm is (empty) which will do no filtering by keyword.

l.) ordering function - in the header (Desc is default, Asc can be chosen). See above point k).

m.) error message functionality: in case of an error while saving/updating a note, a popup will appear, represented by the function: showErrorMessage.

I know, there are lots of points of improvement on the front-end, that was more or less the content of the bootcamp, and I re-created the project for two purposes: practice my Asp.Net Core Web-Api (as opposed to node.js in bootcamp), and Vanilla Javascript skills. Please feel free to comment or come with your own tutorial.



I might come with a next part of the tutorial, mentioning items that have been left out, or going
in more detail in another less discussed area.





Thursday, February 27, 2020

Full Stack Asp.Net Core App (Bootcamp Project) - Part 3 - The Web API

This is the continuation of the part 2 of this series.


Once again, the repository for the full app can be accessed on Github.

The Notes page will be containing most of the operations of the whole application. The notes are displayed via html/css using plain javascript Dom manipulation, and some back-end code in the Web Api Part. The data stored in database is retrieved via Fetch Api, and displayed in the page.
Then the posting of notes accesses the Web API also using Fetch, so does the update and delete ones.

I strongly recommend reviewing the below recommended tutorials before checking this part.

For Ajax/DOM manipulation
1. https://mydev-journey.blogspot.com/2020/02/mybooklist-another-traversy-tutorial.html
2. https://mydev-journey.blogspot.com/2020/02/expense-tracker-from-traversy-media.html
3. https://mydev-journey.blogspot.com/2020/02/full-stack-mini-todo-app-with.html

For the Web api part:
1. the above three tutorials + https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-javascript?view=aspnetcore-3.1
2. https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio

So here I will write the code for the Web Apis created using EF CRUD  (scaffolded automatically using the models presented in previous post, and then edited for additions).

The API controllers are in the Controller folders.

Users - it's completely Scaffolded from the Users Model Class, using the context, no additions written manually.

Notes: 

contains the CRUD operations to create, delete, edit notes.
I recognize, maybe more checks and verification could have been done in the action methods, that could be your homework to do.

Notes Controller

namespace SmartNotes.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class NotesController : ControllerBase
    {
        private readonly SmartNotesDBContext _context;

        public NotesController(SmartNotesDBContext context)
        {
            _context = context;
        }

        // GET: api/Notes
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Notes>>> GetNotes()
        {
            return await _context.Notes.ToListAsync();
        }

        // GET: api/Notes/5   
        [HttpGet("{id}")]
        public async Task<ActionResult<Notes>> GetNotes(int id)
        {
            var notes = await _context.Notes.FindAsync(id);

            if (notes == null)
            {
                return NotFound();
            }

            return notes;
        }
// this is a very important Get action method- retrieving list of notes by user, order and searchstring
        [HttpGet("notesbyuser/{userid}/{order}/{searchstring}")]
        public async Task<ActionResult<List<Notes>>> GetNotesByUser(int userid, string order="Desc", string searchstring="")
        {
            var notes = new List<Notes>();
            if (searchstring == "(empty)") searchstring = "";
            searchstring = searchstring.ToLower();         
            if (order=="Desc")
            {
                notes = await _context.Notes.Where(x => x.Userid == userid).OrderBy(x => x.Pinned).ThenByDescending(x=>x.Createdat).ToListAsync();
            }
            else
            {
                notes = await _context.Notes.Where(x => x.Userid == userid).OrderBy(x => x.Pinned).ThenBy(x => x.Createdat).ToListAsync();
            }

            if (notes == null)
            {
                return NotFound();
            }

            return  notes.Where(x=> x.Title.ToLower().Contains(searchstring) || x.NoteText.ToLower().Contains(searchstring)).ToList();
        }

        // PUT: api/Notes/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        [HttpPut("{id}")]
        public async Task<IActionResult> PutNotes(int id, Notes notes)
        {
            if (id != notes.Id)
            {
                return BadRequest();
            }

            _context.Entry(notes).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!NotesExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/Notes
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.


        [HttpPost]
        public async Task<ActionResult<Notes>> PostNotes(Notes notes)
        {       
                _context.Notes.Add(notes);
                await _context.SaveChangesAsync();
            return CreatedAtAction("PostNotes", new { id = notes.Id }, notes);
        }
// action to pin note 
        [HttpPost("pinnote/{noteid}")]

        public async Task<ActionResult<Notes>> PinNote(int noteid)
        {
            var myNote = await _context.Notes.FindAsync(noteid);
            if (myNote!=null)
            {
                myNote.Pinned = !myNote.Pinned;
                _context.Notes.Update(myNote);
                await _context.SaveChangesAsync();
                return Ok();
            }
            return Ok();
        }
// action to change the color of a note

        [HttpPut("changecolor/{noteid}")]

        public async Task<ActionResult<Notes>> ChangeColor(int noteid, Notes notes)
        {
            var myNote = await _context.Notes.FindAsync(noteid);
            if (myNote != null)
            {
                myNote.Color = notes.Color;
                _context.Notes.Update(myNote);
                await _context.SaveChangesAsync();
                return Ok();
            }
            return Ok();
        }

// a put action to update a note, by id
        [HttpPut("updatenote/{noteid}")]

        public async Task<ActionResult<Notes>> UpdateNote(int noteid, Notes notes)
        {
            var myNote = await _context.Notes.FindAsync(noteid);
            if (myNote != null)
            {
                myNote.Title = notes.Title;
                myNote.NoteText = notes.NoteText;
                _context.Notes.Update(myNote);
                await _context.SaveChangesAsync();
                return Ok();
            }
            return Ok();
        }


        // DELETE: api/Notes/5
// action to delete the note and respective images, by note id
        [HttpDelete("{id}")]
        public async Task<ActionResult<Notes>> DeleteNotes(int id)
        {

            var images = await _context.Images.Where(x => x.Noteid == id).ToListAsync();

            foreach (var img in images)
            {
                var filepath =
                       new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{img.Image}";
                System.IO.File.Delete(filepath);
            }
            if (images!=null) _context.Images.RemoveRange(images);
            var notes = await _context.Notes.FindAsync(id);
            if (notes == null)
            {
                return NotFound();
            }

            _context.Notes.Remove(notes);
            await _context.SaveChangesAsync();

            return Ok();
        }

        private bool NotesExists(int id)
        {
            return _context.Notes.Any(e => e.Id == id);
        }
    }
}

Image Controller - will deal with uploading/deleting images

namespace SmartNotes.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ImagesController : ControllerBase
    {
        private readonly SmartNotesDBContext _context;

        public ImagesController(SmartNotesDBContext context)
        {
            _context = context;
        }

        // GET: api/Images
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Images>>> GetImages()
        {
            return await _context.Images.ToListAsync();
        }

        // GET: api/Images/5
        [HttpGet("{id}")]
        public async Task<ActionResult<Images>> GetImages(int id)
        {
            var images = await _context.Images.FindAsync(id);

            if (images == null)
            {
                return NotFound();
            }

            return images;
        }
// retrieves all images by note id (to display them in the note)
        [HttpGet("imagesbynote/{noteid}")]

        public async Task<ActionResult<List<Images>>> GetImagesByNote(int noteid)
        {
            var images = await _context.Images.Where(x=> x.Noteid ==noteid).ToListAsync();

            if (images == null)
            {
                return NotFound();
            }

            return images;
        }


// retrieves all images by user id (to display them in the note page)
        [HttpGet("imagesbyuser/{userid}")]

        public async Task<ActionResult<List<Images>>> GetImagesByUser(int userid)
        {
            var images =  await _context.Images.ToListAsync();

                if (images == null)
                {
                    return NotFound();
                }

                return images;
        }


        // PUT: api/Images/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        [HttpPut("{id}")]
        public async Task<IActionResult> PutImages(int id, Images images)
        {
            if (id != images.Id)
            {
                return BadRequest();
            }

            _context.Entry(images).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ImagesExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/Images
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        //[HttpPost]
        //public async Task<ActionResult<Images>> PostImages(Images images)
        //{
        //    _context.Images.Add(images);
        //    await _context.SaveChangesAsync();

        //    return CreatedAtAction("GetImages", new { id = images.Id }, images);
        //}

   
// uploading one image, and link it to note having noteid
        [HttpPost("uploadimage/{noteid}")]
        public async Task<ActionResult<Images>> PostUpload( int noteid, IFormFile image)
        {

            if (image != null && noteid!=0 && image.Length > 0 && image.Length < 500000)
            {               
                try
                {

                    var fileName = Path.GetFileName(image.FileName);

                    //Assigning Unique Filename (Guid)
                    var myUniqueFileName = Convert.ToString(Guid.NewGuid());

                    //Getting file Extension
                    var fileExtension = Path.GetExtension(fileName);

                    // concatenating  FileName + FileExtension
                    var newFileName = String.Concat(myUniqueFileName, fileExtension);

                    // Combines two strings into a path.
                    var filepath =
                    new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{newFileName}";

                    using (FileStream fs = System.IO.File.Create(filepath))
                    {
                        image.CopyTo(fs);
                        fs.Flush();
                    }

                    var newImage = new Images();
                    newImage.Image = newFileName;
                    newImage.Noteid = noteid;
                    _context.Images.Add(newImage);
                    await _context.SaveChangesAsync();


                }

                catch (Exception ex)
                {                 
                    return StatusCode(500);
                }
                //Getting FileName


                var myImageList = await _context.Images.Where(x => x.Noteid == noteid).ToListAsync();

                return Ok(myImageList);
            }

            return NoContent();
        }

        // DELETE: api/Images/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<Images>> DeleteImages(int id)
        {
            var images = await _context.Images.FindAsync(id);
            if (images == null)
            {
                return NotFound();
            }

            _context.Images.Remove(images);
            await _context.SaveChangesAsync();

            return images;
        }

// delete images by note, when removing a note
        [HttpDelete("deleteimagesbynote/{noteid}")]
        public async Task<ActionResult<Images>> DeleteImagesByNote(int noteid)
        {
            var images = await _context.Images.Where(x=> x.Noteid == noteid).ToListAsync();
            if (images == null)
            {
                return NotFound();
            }

            foreach (var img in images)
            {
                deleteImage(img.Image);
                _context.Images.Remove(img);
            }

            await _context.SaveChangesAsync();

            return Ok();
        }

        private void deleteImage(string imagefile)
        {
            var filepath =           
                new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Uploads")).Root + $@"\{imagefile}";
         
            System.IO.File.Delete(filepath);
     
        }

        private bool ImagesExists(int id)
        {
            return _context.Images.Any(e => e.Id == id);
        }
    }
}










Sunday, February 23, 2020

Expense-Tracker (from Traversy Media) With Asp.Net Core Back-end


In order to continue my practice of front-end javascript, I copied Brad's repo for the front-end (plain JS version) to check and learn from it.

I transformed it to a full-stack app using Asp.Net Core Razor Pages, In-Memory Database and API Controller.



The application is online: https://expensetracker.zoltanhalasz.net/

My repo is: https://github.com/zoltanhalasz/ExpenseTracker

The main difference is that his version uses the local storage of the browser, whereas mine has back-end using in-Memory database.

I hope that some of you find this useful.

Saturday, February 22, 2020

Full Stack Asp.Net Core App (Bootcamp Project) - Part 1 - Introduction

In the last weeks I decided to review my Javascript front-end lessons from last year's bootcamp. It was a locally organized intensive course, with the aim of hiring those who finish it.
I worked hard to learn javascript and node.js on that course, graduated the bootcamp, but finally remained with my .net projects for my former employer instead of being hired by the organizer of the bootcamp.

For your info, I described the bootcamp in more detail in this post.

Just to review Vanilla JS once again, I decided to re-do the project, this time with Asp.Net Core Backend instead of Node.JS, just to practice my API skills in Asp.Net Also.

The aim of the project is, to do things manually, without the use of any front-end frameworks:
  • writing the pages in plain html, and all styling in css without bootstrap or other systems
  • all interactions with user will be via plain Javascript
  • the back-end Api-s are Asp.Net Core Web API
  • the pages are served via Asp.Net Core Razor Pages
  • database for back-end MS SQL with EF Core (database-first)
Prerequisites for the application and sources for preparation:
App is live under: https://notes.zoltanhalasz.net/

Full code of the app can be found: https://github.com/zoltanhalasz/SmartNotes.git

The SQL to create the database is: under the Github folder above, file: script.sql

I won't promise that I will lead you through the application step by step, because of its complexity and also it's a study project, nothing perfect in it, but can be a great learning tool for you. :)

What the application does not include:
- no special user management, identity, authorizations, no password hashing, only basic cookie-based user login/authentication
- no special protection for the Web APIs
- no Jquery or JS Framework on the frontend, only basic Vanilla JS, with  Fetch for AJAX
- no dashboard or advanced features, statistics
- it is not a perfect application, from the formatting or design/engineering point of view. It is a sample to learn the above mentioned features.


Description of the project
- Manage notes/(small blog posts) of users - add notes: title, content, add color, add images;
Navigate between notes and images, edit existing notes, search and sort notes.
- Signup of Users - collect email, password and name from user
- Login Users - based on name and password

The application has just a couple of pages, in the following logical order:

Index/Home Page
This is the landing page for the application. It's plain html with css written manually, integrated into Razor Pages Index.CsHtml
From this page, users can signup or login.


Signup Page


The design here is also manual, integrated into Razor Pages (no layout). The user can register by filling in basic info.
Login Page


In order to write notes, users must log in, using this page. Very basic design, manually written.
Notes Page


This is the main page of the app, users who are logged in, can create and manage notes. Color can be changed, images can be added and the title/content can be edited for each note. Additionally, searching and ordering the notes is possible.
Error Page



Tuesday, February 18, 2020

Full Stack Mini ToDo-App With Javascript, Ajax, API Controller and In-Memory Database (Asp.Net Core Razor Pages)




During the last couple of days, I decided to revisit my basic DOM Javascript skills, and among other things, decided to write some mini-projects to exercise.

The topics touched in this tutorial are:
1. Front-end Javascript for DOM manipulation
2. Fetch API
3. Web Api controller in Asp.Net Core
4. In-Memory database for EF Core
5. Razor pages project

Materials to follow:
1. Main inspiration of the tutorial was Ajax tutorials from Dennis Ivy (front-end is 90% from him) https://www.youtube.com/watch?v=hISSGMafzvU&t=1157s
2. Repo for the app is:  https://github.com/zoltanhalasz/TodoApp
3. In-Memory database used here (check my materials with Razor pages or https://exceptionnotfound.net/ef-core-inmemory-asp-net-core-store-database/)
4. Web Api - generated from EF Core CRUD automatically in Visual Studio, from the model
5. Application is live under: https://todolist.zoltanhalasz.net/

Main steps of the app:
1. Create Razor Pages App, without authentication

2. Create Class for ToDO
    public class ToDoModel
    {
        public int id { get; set; }
        public string title { get; set; }
        public bool completed { get; set; }
    }
3. Based on the class, the context is created with a table and included in startup.cs. EntityFrameworkCore has to be installed as nuget package.

    public class ToDoContext : DbContext
    {
        public ToDoContext(DbContextOptions<ToDoContext> options)
            : base(options)
        {
        }

        public DbSet<ToDoModel> ToDoTable { get; set; }
    }

and in the ConfigureServices method/startup.cs

 services.AddDbContext<ToDoContext>(options => options.UseInMemoryDatabase(databaseName: "ToDoDB"));
         
4. Add a Controller folder, then scaffold Web-api (CRUD with EF Core), can be done based on above class and Context.



5. Front-End, content of index.cshtml file:


Tuesday, January 21, 2020

Missing Pieces in My Learning for 2020

If you read my posts here, it can be seen that my main technology used is Microsoft's .Net with C#.
My main apps are WPF, with Dapper ORM/SQL, and a small module in Asp.Net core Razor pages. But I still feel I have many missing parts in my learning process. I am finding the following tutorials really interesting to pursue, and cover these areas. I started them, but didn't go through them in detail. I am glad I found these, and this is the plan for me during the first half of 2020.



A. Identity Framework for Asp.Net Core.
I did some small tutorials this far on this topic, and lately I found this one. Code Maze is the best at C# tutorials, and this is my next one on Identity:
https://code-maze.com/identity-asp-net-core-project/
I feel there are missing pieces for me in this framework, and please let me have new materials if you find some.

B. Entity Framework Core
I used EF Core in my Asp.Net web project, but I still think that there are things to be learnt here. I found Tim's newest tutorial on this, quickly checked it but didn't have time to go deeply... Another one to check more in detail.
https://www.youtube.com/watch?v=qkJ9keBmQWo

C. Asynchronous C# with Async/Await
I started using this in my Asp and WPF project, and indeed it added some responsiveness to my apps. Need to study this deeper. Any ideas on this are welcome!

D. Logging in Asp.Net Core.
I never used this in my apps, but I plan to. The greatest source for this will be my newly purchased course on Asp.Net core 3: https://code-maze.com/ultimate-aspnet-core-3-web-api/

E. Check out Blazor, especially server side, later on, this year.

F. Some more Javascript on the front-end, and then my framework of choice, Angular. I am very behind in my learning with Angular, partly because I have to work on my projects. Currently I use some JQuery tools on my Asp.net project, like datatables.js, pivotjs, chart.js, and I really like what I discovered. Any new ideas are welcome.

Sunday, December 29, 2019

2019 - The Year of .Net (Core) and Javascript. My New Directions for 2020

My real developer journey began in March 2019, when I decided to go full time developing my business applications. Before, I was doing this in parallel with my management accountant job, which was very exhausting at times.
The transition had lots of lessons, and it's described in my posts here on this blog and also on dev.to, https://dev.to/zoltanhalasz
But as a conclusion for 2019, some big trends can be seen in my work and learning, and these are the two main directions:
The Microsoft .Net Framework
Being the first choice for accounting applications, as the users all operate in Windows environments, I think this was a good decision. In fact, my then partner suggested the C#/WPF/MVVM track with MS SQL database.
Later during fall of 2019, I extended this with Asp.Net Core, as you can see in my posts, and that's the direction I want to follow in 2020.
Why I chose the asp.net core world? Reasons:
Some new directions for 2020 to experiment:
  • the Blazor framework, especially server-side, than later client-side.
The Web Programming Track with JS
As I mentioned in my blog posts, the web with Javascript was a real discovery for me in 2019. I really like the flexibility of JS and its huge impact on the front-end (plain JS, JQuery or SPA), which I try to implement in my projects, to make user experience better, and simulate a real business tool environment with grids, menus, pivot tables, charts and excel exports/imports.
Ways to improve my JS skills and integrate them to my tools
  • find new JQuery plugins for a great business tool feel;
  • maybe go deeper with SPA such as Angular (my journey began with this framework);
  • researching tools/frameworks/libraries for reporting/charting/grids;
Not to forget, the topic of database persistence, it will probably remain the MS SQL world, using Dapper ORM and EF Core, maybe with some experimenting with My SQL/ Mongo DB.
Another idea worth mentioning for 2020, will be a try of serverless functions from Azure.
And lastly to mention, if and when I have time, will be the Angular/Material design/Firebase world, which I really liked during my experimenting in the first half of 2019.
What do you think, would you add something different for my business app stack?

Wednesday, December 11, 2019

DataTable.js Tutorial for .Net Core Razor Pages Application - Part 2 - Full CRUD



This is the continuation of the Part 1 tutorial. The main goal here is to apply rendered elements in the datatable, which can contain various HTML tags, such as links/operations with other pages.

Technologies used:
1. Javascript, Datatables.js
2. Razor Pages, .Net Core
3. In-Memory Database in .Net Core

Prerequisites:
1. Asp.Net Core 2.2  Razor Pages, see suggested learning: https://mydev-journey.blogspot.com/2019/11/razor-pages-not-for-shaving.html
2. In-memory Database, presented in tutorial: https://exceptionnotfound.net/ef-core-inmemory-asp-net-core-store-database/
3. I was inspired by this tutorial: https://www.c-sharpcorner.com/article/using-datatables-grid-with-asp-net-mvc/
4. See Part 1, which is the more simple approach for DataTables:  https://mydev-journey.blogspot.com/
5. Link for Part 2 Repository, zipped: https://drive.google.com/open?id=1PT9Tk77m2gfZVrFmLwefSt_lqXuYyvEr
6. setup the wwwroot folder, in a similar way as for the Part 1 tutorial
7. you can view the application live online: http://datatables.azurewebsites.net/

Steps:
1. Create Razor Web Project
2. Create Base Class:
    public class InvoiceModel
    {
        [JsonProperty(PropertyName = "ID")]
        public int ID { get; set; }
        [JsonProperty(PropertyName = "InvoiceNumber")]
        public int InvoiceNumber { get; set; }
        [JsonProperty(PropertyName = "Amount")]
        public double Amount { get; set; }
        [JsonProperty(PropertyName = "CostCategory")]
        public string CostCategory { get; set; }
        [JsonProperty(PropertyName = "Period")]
        public string Period { get; set; }   
    }
3. Create and Populate in-memory database and table
Create Context:
    public class InvoiceContext : DbContext
    {
        public InvoiceContext(DbContextOptions<InvoiceContext> options)
            : base(options)
        {
        }

        public DbSet<InvoiceModel> InvoiceTable { get; set; }
    }
Create Invoice Generator Service
 public class InvoiceGenerator
    {
      public static void Initialize(IServiceProvider serviceProvider)
        {
            using (var context = new InvoiceContext(serviceProvider.GetRequiredService<DbContextOptions<InvoiceContext>>()))
            {
                // Look for any board games.
                if (context.InvoiceTable.Any())
                {
                    return;   // Data was already seeded
                }

                context.InvoiceTable.AddRange(
                new InvoiceModel() { ID=1, InvoiceNumber = 1, Amount = 10, CostCategory = "Utilities", Period = "2019_11" },
                new InvoiceModel() { ID=2, InvoiceNumber = 2, Amount = 50, CostCategory = "Telephone", Period = "2019_12" },
                new InvoiceModel() { ID = 3, InvoiceNumber = 3, Amount = 30, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 4, InvoiceNumber = 4, Amount = 40, CostCategory = "Consultancy", Period = "2019_11" },
                new InvoiceModel() { ID = 5, InvoiceNumber = 5, Amount = 60, CostCategory = "Raw materials", Period = "2019_10" },
                new InvoiceModel() { ID = 6, InvoiceNumber = 6, Amount = 10, CostCategory = "Raw materials", Period = "2019_11" },
                new InvoiceModel() { ID = 7, InvoiceNumber = 7, Amount = 30, CostCategory = "Raw materials", Period = "2019_11" },
                new InvoiceModel() { ID = 8, InvoiceNumber = 8, Amount = 30, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 9, InvoiceNumber = 8, Amount = 20, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 10, InvoiceNumber = 9, Amount = 2, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 11, InvoiceNumber = 10, Amount = 24, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 12, InvoiceNumber = 11, Amount = 10, CostCategory = "Telephone", Period = "2019_11" },
                new InvoiceModel() { ID = 13, InvoiceNumber = 12, Amount = 40, CostCategory = "Consultancy", Period = "2019_12" },
                new InvoiceModel() { ID = 14, InvoiceNumber = 13, Amount = 50, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 15, InvoiceNumber = 14, Amount = 40, CostCategory = "Utilities", Period = "2019_11" },
                new InvoiceModel() { ID = 16, InvoiceNumber = 15, Amount = 10, CostCategory = "Services", Period = "2019_11" });

                context.SaveChanges();
            }

        }


4. Register the database
within Startup cs, above add MVC command:
            services.AddDbContext<InvoiceContext>(options => options.UseInMemoryDatabase(databaseName: "InvoiceDB"));

within Program Cs, we need to make changes, see final version:
 public class Program
    {
        public static void Main(string[] args)
        {


            var host = CreateWebHostBuilder(args).Build();

            //2. Find the service layer within our scope.
            using (var scope = host.Services.CreateScope())
            {
                //3. Get the instance of BoardGamesDBContext in our services layer
                var services = scope.ServiceProvider;
                var context = services.GetRequiredService<InvoiceContext>();

                //4. Call the DataGenerator to create sample data
                InvoiceGenerator.Initialize(services);
            }
            //Continue to run the application
            host.Run();
            //CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
5. Using EF, the tables are populated in all Pages, see example for Index:

PageModel:
  public class IndexModel : PageModel
    {
        private InvoiceContext _context;

        public List<InvoiceModel> InvoiceList;
        public IndexModel(InvoiceContext context)
        {
            _context = context;
        }
        public void OnGet()
        {
            InvoiceList = _context.InvoiceTable.ToList();
        }
    }
CSHTML file
will be a simple listing of the InvoiceTable using foreach (actually you can scaffold this view)
6. DataTableArrayRender page:
Will contain the datatable js code, together with the rendered html elements:

@page
@model DataTableArrayRenderModel
@{
    ViewData["Title"] = "Invoice List - With Datatable - from Javascript Array";
}

    <div class="text-center">
        <h1 class="display-4">Show DataTable - from Javascript Array -  Rendered Columns</h1>
        <p>
            <a asp-page="Index">Show original Table (Html from Razor)</a>
        </p>
        <p>
            <a asp-page="InvoiceAdd" class="btn btn-info">Add New Invoice</a>
        </p>
    </div>

<script type="text/javascript" language="javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/jquery.dataTables.min.js"></script>

<script>
    /////////
    function convertToDataSet(responseJSON) {
        console.log(responseJSON);
        var returnList = [];
        var returnitem = [];
        for (var i = 0; i < responseJSON.length; i++) {
            console.log(responseJSON[i]);
            returnitem = [];
            returnitem.push(responseJSON[i].ID);
            returnitem.push(responseJSON[i].InvoiceNumber);
            returnitem.push(responseJSON[i].Amount);
            returnitem.push(responseJSON[i].CostCategory);
            returnitem.push(responseJSON[i].Period);
            returnList.push(returnitem);
        }
        return returnList;
    }

    function getTable() {
        return fetch('./DataTableArrayRender?handler=ArrayDataRender',
            {
                method: 'get',
                headers: {
                    'Content-Type': 'application/json;charset=UTF-8'
                }
            })
            .then(function (response) {
                if (response.ok) {
                    return response.text();
                } else {
                    throw Error('Response Not OK');
                }
            })
            .then(function (text) {
                try {
                    return JSON.parse(text);
                } catch (err) {
                    throw Error('Method Not Found');
                }
            })
            .then(function (responseJSON) {
                var dataSet = convertToDataSet(responseJSON);
                console.log(dataSet);
                $(document).ready(function () {
                    $('#example').DataTable({
                        data: dataSet,
                        "processing": true, // for show progress bar
                        "filter": true, // this is for disable filter (search box)
                        "orderMulti": false, // for disable multiple column at once
           
                        columns: [
                            { title: "ID" },
                            { title: "InvoiceNumber" },
                            { title: "Amount" },
                            { title: "CostCategory" },
                            { title: "Period" },
                            {
                                data: null, render: function (data, type, row) {                                 
                                    return '<a class="btn btn-danger" href="/InvoiceDelete?id=' + row[0] + '">Delete</a>';
                                }
                            },
                            {
                                "render": function (data, type, full, meta)
                                { return '<a class="btn btn-info" href="/InvoiceEdit?id=' + full[0] + '">Edit</a>'; }
                            },
                            {
                                "render": function (data, type, full, meta)
                                { return '<a class="btn btn-warning" href="/Index">Main Page</a>'; }
                            },
                        ]
                    });
                });
            })
    }

    getTable();
</script>

<table id="example" class="display" width="100%"></table>

7. Using the InvoiceModel, we can scaffold all pages like Delete, Create, Edit using EF model scaffolding of Razor Pages.
8. The end result will be a nice navigation table that besides the invoice data, will contain the rendered buttons/links.
End result:


DataTable.js Tutorial for .Net Core Razor Pages Application - Part 1


As I mentioned in my previous posts, my goal is to gather a number of  open source libraries, that can be implemented in my .NET Core project, alongside with Razor pages. My projects have to serve business purposes, so they need certain features that make them similar to Microsoft Excel.

Until now, I found a solution for:
a. Charting, with Chart.js, shown in tutorial: https://mydev-journey.blogspot.com/2019/12/chartjs-tutorial-with-aspnet-core-22.html
b. Pivot Table, with PivotTable.js, shown in tutorial: https://mydev-journey.blogspot.com/2019/12/pivottablejs-in-net-core-razor-pages.html
c. Fast Report, open source reporting tool, will be presented soon;
d. DataTables - present tutorial.
e. AgGrid or other grid systems - future plan.

In the Javascript opensource world, https://datatables.net/ seems to be a respected solution for grids on the frontend. This is what I want to learn and implement in my project, and in the meantime, to share with you. I would like to study later a more advanced solution with datatables, with additional features, and perhaps that will be a continuation for this tutorial, another time.


Materials to study:
I. DataTables site, and two small examples that are the backbone of this tutorial:
a. https://datatables.net/examples/data_sources/dom.html
b. https://datatables.net/examples/data_sources/js_array.html
II. My Razor projects with Chart and PivotTable, see my tutorials at a) and b) above, see link:  https://mydev-journey.blogspot.com/2019/12/pivottablejs-in-net-core-razor-pages.html
III. Download my code from zipped repo: https://drive.google.com/open?id=1g1eJkqV1dphV0iAPQiqpLJSQtt_iYjeX
IV. Download the zipped DataTables files from: https://datatables.net/download/
Download the Css and JS file specific to Datatables, and place them into the CSS and JS folder of wwwroot.

jquery.dataTables.min.css => will go to the wwwroot/css folder
jquery.dataTables.min.js => will go to the wwwroot/js folder
then, copy the images files to wwwroot/images


Steps to follow for this introductory tutorial:
1. Create a .Net Core Web Project (Razor pages)
2. Create Base Class - InvoiceModel
    public class InvoiceModel
    {
        [JsonProperty(PropertyName = "InvoiceNumber")]
        public int InvoiceNumber { get; set; }

        [JsonProperty(PropertyName = "Amount")]
        public double Amount { get; set; }
        [JsonProperty(PropertyName = "CostCategory")]
        public string CostCategory { get; set; }
        [JsonProperty(PropertyName = "Period")]
        public string Period { get; set; }   

    }

3. Create Service to Populate List of  Invoices.

 public class InvoiceService
    {
        public List<InvoiceModel> GetInvoices()
        {
            return new List<InvoiceModel>()
            {
                new InvoiceModel() {InvoiceNumber = 1, Amount = 10, CostCategory = "Utilities", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 2, Amount = 50, CostCategory = "Telephone", Period="2019_12"},
                new InvoiceModel() {InvoiceNumber = 3, Amount = 30, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 4, Amount = 40, CostCategory = "Consultancy", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 5, Amount = 60, CostCategory = "Raw materials", Period="2019_10"},
                new InvoiceModel() {InvoiceNumber = 6, Amount = 10, CostCategory = "Raw materials", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 7, Amount = 30, CostCategory = "Raw materials", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 8, Amount = 30, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 8, Amount = 20, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 9, Amount = 2, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 10, Amount = 24, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 11, Amount = 10, CostCategory = "Telephone", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 12, Amount = 40, CostCategory = "Consultancy", Period="2019_12"},
                new InvoiceModel() {InvoiceNumber = 13, Amount = 50, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 14, Amount = 40, CostCategory = "Utilities", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 15, Amount = 10, CostCategory = "Services", Period="2019_11"},
            };
        }
    }

This will be injected in the pages where the list is needed.

We need to register in the services, startup.cs, just above the AddMvc command.
            services.AddTransient<InvoiceService>();

4. Create special layout file, which inserts the necessary css file in the head of the new Layout file.
in the head, this will be inserted:
    <link rel="stylesheet" href="~/css/jquery.dataTables.min.css" /> 

See _DataTableLayout from my repo.

4. Index page: will display a html table with the elements of the list above, using the classic Razor Page syntax.


5. DataTable page will contain the JS Code to transform an existing Html table to the DataTable grid format, according to study material I - a) above.

in the Razor Page, the following Javascript code will be inserted, below the listing of the table:

<script type="text/javascript" language="javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/jquery.dataTables.min.js"></script>
<script>
    $(document).ready(function () {
        $('#example').DataTable({
            "order": [[3, "desc"]]
        });
    });
</script>

Important aspect here: the html table has to have the id "example" to be transformed into the grid format by the datatable js commands.

6. DataTableAjax will use AJAX Fetch in javascript to generate an array which will be used as a datasource for the array, according to study material I - b) above.

<script type="text/javascript" language="javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/jquery.dataTables.min.js"></script>

<script>
    /////////
    function convertToDataSet(responseJSON) {
        console.log(responseJSON);
        var returnList = [];
        var returnitem = [];
        for (var i = 0; i < responseJSON.length; i++) {
            console.log(responseJSON[i]);
            returnitem = [];
            returnitem.push(responseJSON[i].InvoiceNumber);
            returnitem.push(responseJSON[i].Amount);
            returnitem.push(responseJSON[i].CostCategory);
            returnitem.push(responseJSON[i].Period);
            returnList.push(returnitem);
        }
        return returnList;
    }

    function getTable() {
        return fetch('./DataTableArray?handler=ArrayData',
            {
                method: 'get',
                headers: {
                    'Content-Type': 'application/json;charset=UTF-8'
                }
            })
            .then(function (response) {
                if (response.ok) {
                    return response.text();
                } else {
                    throw Error('Response Not OK');
                }
            })
            .then(function (text) {
                try {
                    return JSON.parse(text);
                } catch (err) {
                    throw Error('Method Not Found');
                }
            })
            .then(function (responseJSON) {
                var dataSet = convertToDataSet(responseJSON);
                console.log(dataSet);
                $(document).ready(function () {
                    $('#example').DataTable({
                        data: dataSet,
                        columns: [
                            { title: "InvoiceNumber" },
                            { title: "Amount" },
                            { title: "CostCategory" },
                            { title: "Period" },
                        ]
                    });
                });
            })
    }

    getTable();
</script>

<table id="example" class="display" width="100%"></table>


7. The final result will be:
a. from html table:


















b. from Javascript Array: