Youtube channel !

Be sure to visit my youtube channel
Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Friday, August 07, 2020

NodeJS JSON API + MongoDB + JWT + ES6 forms

Here is how to create a real-life NodeJS API together with a login form.

Resources:

JavaScript for beginners - learn by doing

Learn Node.js, Express and MongoDB + JWT

We will start with the HTML representing the form as well as its JavaScript functionality:

formLogin.html


  <html>
<body>
<form id="myform">
<div>
<label for="email">Email:</label>
<input type="text" id="email" name="email" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</div>
<div class="button">
<button type="submit" id="loginUser">Send</button>
</div>
</form>

<div id="result"></div>

<script type="text/javascript">
async function fetchData(url = '', data = {}, method, headers = {}) {
const response = await fetch(
url, {
method,
headers: { 'Content-Type': 'application/json', ...headers },
...data && { body: JSON.stringify(data) },
});
return response.json();
}

let form = document.querySelector('#myform');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
fetchData(
'/user/login',
{ email: this.email.value, password: this.password.value },
'POST'
).then((result) => {
if (result.token) {
// request the url with token
fetchData('/info', null, 'GET', { Bearer: result.token })
.then((result) => { console.log(result); });
return;
}
document.querySelector('#result').innerHTML = `message: ${result.message}`;
})
.catch(error => console.log('error:', error));
})
}
</script>
</body>
</html>



our main node server: index.js

import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";

// import the routes
import routes from "./routes/routes.js";

// create an express instance
const app = express();

app.use(express.json())

// setup the middleware routes
routes(app);

// config the database credentials
dotenv.config();

// connect to the database
mongoose.connect(
process.env.DB_CONNECT,
{ useNewUrlParser: true, useUnifiedTopology: true },
() => console.log("connected to mongoDB")
);
// listen for errors
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
// listen on port 3000
app.listen(3000, () => console.log("server is running"));

application routes: routes.js

import { loginUser } from "../controllers/controller.js";
import { info } from "../controllers/info.js"; // the protected route
import { auth } from "../controllers/verifyToken.js"; // middleware for validating the token

import * as path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url); // The absolute URL of the current file.
const __dirname = path.dirname(__filename); // parse just the directory


const routes = app => {
app.route("/user/login").get((req, res) => { res.sendFile('formLogin.html', { root: path.join(__dirname, "../views") }); });
app.route("/user/login").post((req, res) => loginUser(req, res)); // we capture inside req, and res

app.route("/info").get(auth, (req, res) => info(req, res)); // we capture inside req, and res
// and insert the auth middleware to process the token
};
export default routes;


our main controller: controller.js

import mongoose from "mongoose";
mongoose.set("useCreateIndex", true);
import { userSchema } from "../models/user.js";
import jwt from "jsonwebtoken";

const User = mongoose.model("users", userSchema); // users is the name of our collection!
export const addNewUser = (req, res) => {
User.init(() => {
// init() resolves when the indexes have finished building successfully.
// in order for unique check to work

let newUser = new User(req.body); // just creating w/o saving
newUser.password = newUser.encryptPassword(req.body.password);

newUser.save((err, user) => { // now saving
if (err) {
res.json({ 'message': 'duplicate email' });
}
res.json(user);
});
});
};

export const loginUser = (req, res) => {

if (req.body.password == null || req.body.email == null) {
res.status(400).json({ 'message': 'Please provide email / password' });
return;
}

User.init(() => {
User.findOne({ email: req.body.email }, (err, user) => {
if (err) {
res.json(err);
return;
}
if (user == null) {
res.status(400).json({ 'message': 'Non existing user' });
return;
});

// here user is the fetched user
const validPassword = user.validatePassword(req.body.password, user.password);

if (!validPassword) {
res.status(400).json({ 'message': 'Not valid password' });
return;
}

// create and send a token to be able to use it in further requests
const token = jwt.sign({ _id: user._id }, process.env.TOKEN_SECRET);
res.header("auth-token", token) // set the token in the header of the response
.json({ 'token': token }); // display the token
});
});
};



js helper middleware for working with JWT tokens: verifyToken.js

    import jwt from "jsonwebtoken";
export const auth = (req, res, next) => {
const token = req.header("Bearer");
if (!token) return res.status(401).json({'message':'access denied'});
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if (!verified) res.status(400).message({'message':'Invalid token'});
// continue from the middleware to the next processing middleware :)
next();
};


user database model: user.js

import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';

let userSchema = new mongoose.Schema(
{
email: {
type: String,
requires: "Enter email",
maxlength: 50,
unique: true
},
password: {
type: String,
required: "Enter password",
maxlength: 65
}
},
{
timestamps: true
}
);

userSchema.method({
encryptPassword: (password) => {
return bcrypt.hashSync(password, bcrypt.genSaltSync(5));
},
validatePassword: (pass1, pass2) => {
return bcrypt.compareSync(pass1, pass2);
}
});

export { userSchema };

Congratulations !

Sunday, May 31, 2020

Compositon in JavaScript


Be sure to check out this JavaScript course.
Here is an example of Angular component using a template decorator in TypeScript:

@Component({
template: '<div>Woo a component!</div>',
})
export class ExampleComponent {
constructor() {
console.log('Hey I am a component!');
}
}


In JavaScript a decorator can be viewed as a composite with only one component and it isn’t intended for object aggregation. Here is the  Decorator pattern in action:

const setTemplate = (component) => {
// override
component.template += '<p>new information</p>';
}

const component = {
template: "<div>hello</div>",
};
setTemplate(component); // pass the whole object to the setTemplate function

console.log(component.template);


Enter Mixins:
They find good usage base for object aggregation as well as inside of multiple components, at the same time have some drawbacks:

const externalLib = {
// ... other functions we use
setTemplate: () => { console.log('overriding...'); } // overriding function
}


Introducing partial composition using inheritance mixin:

const myComponent = Object.assign(
Properties of the target object are overwritten by properties of the source object, if they have the same key. This way later sources' properties overwrite earlier ones.
{
setTemplate: () => { console.log('original'); } // initially in our object will be overriden from ExternalLib
},
externalLib
)
myComponent.setTemplate();

We can update the mixin code, but this solves just half way the problem, as this time our function will overwrite the externalLib functionality:

const myComponent = Object.assign({}, externalLib, {
setTemplate: () => { console.log('overriding externalLib...'); } // when composing objects using .assign() last properties take precedence
});


Composition: solving the override (fragile base) problem

const externalLib = {
// ... other functions we use
setTemplate: () => { console.log('overriding original...'); } // overriding function
}

const myComponent = {
setTemplate: () => { console.log('original'); },
externalLib
}

myComponent.setTemplate();

This way our object contains, rather than mixes, the library object. And now there's no more fragile base problem.


Piping example, keep in mind that it is mutable or mutates the properties of the composed object:
const pipe = (...funcs) => initialArg => funcs.reduce((acc, func) => func(acc), initialArg);

const setTemplate = () => {
return "<div>hello</div>"
};

const setName = key => arg => {
return `${arg} + ${key}!`;
};

const Component = pipe(
setTemplate,
setName('John'),
);

const component = Component();
console.log(component);


Updating the piping using states, making the state immutable:

const pipe = (...funcs) => initialArg => funcs.reduce((acc, func) => func(acc), initialArg);

const setTemplate = (state = {}) => { // create a state on the first run
return { // return copy of the state object (immutability!)
...state,
change: inputTemplate => { // template is input parameter
state.template = InputTemplate;
return state;
},
}
}

const setLogin = (state = {}) => { // receive the state as parameter or if not create an empty one
return {
...state,
login: () => {
console.log('Logged in!')
},
}
}


const createComponent = (name, template) => { // factory function
const component = { name, template } // initial object
return pipe(
setLogin,
setTemplate,
)(component)
}


let component = createComponent('hello_user', '<div></div>');
console.log("initial: " + JSON.stringify(component));

let newState = component.change("! new template !"); // immutable change creates a new object
console.log('changed to' + JSON.stringify(newState));
console.log('original component' + JSON.stringify(component));


Thanks for reading!

Sunday, January 26, 2020

JavaScript form validation with promises and exceptions handling

It is good to know the basics of handling web forms. The client entered data usually requires initial validation handled by the JavaScript interpreter of the browser. Here is how to approach the aspects of validating and sending data, as well as parsing the returned server response with the help of JavaScript built-in features such as promises and exceptions. More on them you can learn in this JavaScript course.


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    #info {
      opacity: 1;
      transition: opacity 5s;
    }

    #info.hidden {
      opacity: 0;
    }
  </style>
</head>

<body>

  <form id="user-input">
    <div class="form-control">
      <label for="username">Username</label>
      <input type="text" id="username" />
    </div>
    <div class="form-control">
      <label for="password">Password</label>
      <input type="password" id="password" />
    </div>
    <button type="submit" id="submit" disabled>Create User</button>
  </form>

  <span id="info"></span>


<script>
  const REQUIRED = 'REQUIRED';
  const MIN_LENGTH = 'MIN_LENGTH';

  function isValid(value, attr, validatorValue) {
    if (attr === REQUIRED) { return value.trim().length > 0; }
    if (attr === MIN_LENGTH) { return value.trim().length > validatorValue; }
  }

  function createUser(userName, userPassword) {
    if (!isValid(userName, REQUIRED) || !isValid(userPassword, MIN_LENGTH, 5)) {
      throw new Error('Wrong username or password.'); // client-side check
    }
    // return the server-side check promise
    return new Promise(function (resolve, reject) {
      // db checking logic
      is_created = true;
      if (is_created) {
        resolve(
          {
            userName: userName,
            createdAt: +new Date,
            message: 'successfully created user'
          }
        )
      }
      else {
        reject(
          {
            message: 'not valid user or password'
          }
        );
      }
    });
  }

  function displayInfo(info) {
    const info_el = document.querySelector('#info');
    info_el.innerHTML = JSON.stringify(info);
    info_el.classList.toggle('hidden');
  }

  function signupHandler(event) {
    event.preventDefault();
    try {
      createUser(enteredUsername.value, enteredPassword.value)
        .then( // return from the server-side promise
          (info) => { displayInfo(info); },
          (error) => { displayInfo(error); }
        );
    } catch (err) { displayInfo(err.message); } // return from client-side error (throw)
    // .message is auto generated from the throw error object
  }

  const form = document.querySelector('#user-input');
  form.addEventListener('submit', signupHandler);

  // access the form controls
  const submitBtn = form.querySelector('#submit');
  const enteredPassword = form.querySelector('#password');

  const enteredUsername = form.querySelector('#username');
  enteredUsername.addEventListener('input', () => {
    // this.value points to window object, because => preserve the parent context
    submitBtn.disabled = !isValid(enteredUsername.value, REQUIRED);
  });

</script>

</body>
</html>

Congratulations and enjoy learning JavaScript!

Thursday, January 16, 2020

JSON web tokens (JWT) with NodeJS REST API

Here is how to deal with JWT inside REST API routes:
Note: if you want to learn more on JSONWebTokens and REST you can visit this course: Learn Node.js, Express and MongoDB + JWT

 


So let's begin by creating a new directory project:
mkdir auth
setup the project:
npm init -f
Then we will install the following libraries:
for setting up the webserver
npm i express
for the database connection
npm i mongoose
for reading .env files
npm i dotenv
for restarting the nodejs application(webserver)
npm i --save-dev nodemon
for using ES6 syntax inside nodejs
npm i --save-dev @babel/preset-env @babel/core @babel/node
setting up the transpiling inside babel
nano .babelrc
{"presets": ["@babel/preset-env"]}

install eslint
npm install eslint --save-dev
other packages: bcryptjs, jsonwebtoken

to be able to run the code change package.json:
"start": "nodemon --exec babel-node index.js"
start developing from the current directory inside visual studio code:
code .

.env file
DB_CONNECT ="mongodb://127.0.0.1/users"
TOKEN_SECRET = "onetwothreefourfive"

our index.js file:
import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";
// import the routes
import routes from "./routes/routes";

// create an express instance
const app = express();

// setup the middleware routes
routes(app);

// config the database credentials
dotenv.config();

// connect to the database
mongoose.connect(
process.env.DB_CONNECT,
{ useNewUrlParser: true, useUnifiedTopology: true },
() => console.log("connected to mongoDB")
);
// listen for errors
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
// listen on port 3000
app.listen(3000, () => console.log("server is running"));


controller.js:
import mongoose from "mongoose";
mongoose.set("useCreateIndex", true);
import { userSchema } from "../models/user.js";
import * as bcrypt from "bcryptjs";
import * as jwt from "jsonwebtoken";

const User = mongoose.model("users", userSchema); // users is the name of our collection!!!
export const addNewUser = (req, res) => {
User.init(() => {
// init() resolves when the indexes have finished building successfully.
// in order for unique check to work
let newUser = new User(req.query); // just creating w/o saving
newUser.password = bcrypt.hashSync(req.query.password, 10); // setting password synchronously
newUser.save((err, user) => { // now saving
if (err) {
res.send(err.message);
}
res.json(user);
});
});
};

export const loginUser = (req, res) => {
User.init(() => {
User.findOne({ email: req.query.email }, (err, user) => {
if (err) {
res.send(err);
}
if (user == null) {
res.status(400).send("Non existing user");
}

// we have the user record from db, now check the password
const validPassword = bcrypt.compareSync(
req.query.password,
user.password
);
if (!validPassword) res.status(400).send("Not valid password");

// create and send a token to be able to use it in further requests
const token = jwt.sign({ _id: user._id }, process.env.TOKEN_SECRET);
res.header("auth-token", token)  // set the token in the header of the response

.send(token); // display the token
});
});
};


routes.js: // our main routes file
import { addNewUser, loginUser } from "../controllers/controller.js";
import { info } from "../controllers/info.js"; // the protected route

import { auth } from "../controllers/verifyToken"; // middleware for validating the token

const routes = app => { 
app.route("/user/register").get((req,res)=>addNewUser(req,res)); // we capture inside req, and res
app.route("/user/login").get((req,res)=>loginUser(req,res)); // we capture inside req, and res
app.route("/info").get(auth,(req,res)=>info(req,res)); // we capture inside req, and res
// and insert the auth middleware to process the token
};
export default routes;


verifytoken.js
import * as jwt from "jsonwebtoken";

export const auth = (req, res, next) => {
const token = req.header("Bearer");
if (!token) return res.status(401).send("access denied");
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
if (!verified) res.status(400).send("Invalid token");
// continue from the middleware to the next processing middleware :)
next();
};

// user mongoDB schema:
user.js
import mongoose from "mongoose";
export const userSchema = new mongoose.Schema(
{
name: { type: String, required: "Enter username", minlength: 5, maxlength: 20 },
email: { type: String, required: "Enter email", maxlength: 50, unique: true },
password: { type: String, required: "Enter password", maxlength: 65 }
},
{
timestamps: true
}
);

Congratulations and enjoy learning !

Sunday, December 29, 2019

Star rating script - JavaScript and CSS

There is a whole course on how to achieve star rating using PHP and JavaScript, and here is a simple way consisting of CSS and JavaScript only:


<style>
.rating {
display: flex;
padding: 0;
margin: 0;
}

.rating li {
list-style-type: none
}

.rating-item {
border: 1px solid #fff;
cursor: pointer;
font-size:2em;
color: yellow;
}

/* initial: make all stars full */
.rating-item::before {
content: "\2605";
}

/* make until the clicked star (the rest) empty */
.rating-item.active ~ .rating-item::before {
content: "\2606";
}

/* on hover make all full */
.rating:hover .rating-item::before {
content: "\2605";
}

/* make until the hovered (the rest) empty */
.rating-item:hover ~ .rating-item::before {
content: "\2606";
}

</style>

&lt!--html markup-->

<ul class="rating">
<li class="rating-item" data-rate="1"></li>
<li class="rating-item active" data-rate="2"></li>
<li class="rating-item" data-rate="3"></li>
<li class="rating-item" data-rate="4"></li>
<li class="rating-item" data-rate="5"></li>
</ul>


<script>
const container = document.querySelector('.rating');
const items = container.querySelectorAll('.rating-item')
container.onclick = e => {
const elClass = e.target.classList;
// change the rating if the user clicks on a different star
if (!elClass.contains('active')) {
items.forEach( // reset the active class on the star
item => item.classList.remove('active')
);
console.log(e.target.getAttribute("data-rate"));
elClass.add('active'); // add active class to the clicked star
}
};
</script>

Congratulations !

Wednesday, December 25, 2019

Sorted to do list in JavaScript

JavaScript is a wonderful language as you may discover in the JavaScript for beginners - learn by doing course.

Here is a short working example on how we can easily sort todos saved inside an array:

The initial HTML markup:
<input id="myInput" type="text" /><button id="Add"> Add new LI </button>
<ul class="todoList"></ul>

<script>
// initial array to hold the todos
const todos = [];

// we have helper function for doing the actual sorting
const doSort = (todos) => {
return todos
// .map(todo => todo.toLowerCase()) // make all the items lowercase
.sort((a, b) => {
// compare 2 words letter by letter
if (a.value > b.value) { return 1; } // when the first letter is after the second
if (a.value < b.value) { return -1; } // when the second letter is before the second
return 0; // if both letters are the same
 }) 
} // attach event listener to the Add button document.querySelector('#Add').addEventListener('click', () => {

// get what's inside the input
const data = document.querySelector('#myInput');

// push the new todo into the todos array
todos.push(data.value);

// create additional helper array with object values and indexes
var mapped = todos.map(
(el, i) => ({ index: i, value: el.toLowerCase() })
);

// sort the todos
const sortedTodos = doSort(mapped)
// restore the originals from the todos array
.map(el => todos[el.index]);

// display the sorted totos
todoList.innerHTML = sortedTodos.map(todo => '<li>' + todo + '</li>').join('');

// clear the value of the input
data.value = '';
});

//get reference to the todoList
const todoList = document.querySelector('.todoList');

</script>

Congratulations!

Resources:

JavaScript for beginners - learn by doing

Thursday, December 19, 2019

To do list in Angular

Here is how to create a simple todo-list in Angular. We will be also using extensively RxJs BehaviorSubject as well as Observables. If you want more information on the techniques used, I advise you to practice with the full Angular for beginners course.




First install angular with: sudo npm i -g @angular/cli
Then create a new todo project: ng new todo
Inside the todo directory create 2 new components and service using the Angular CLI:
ng g c todos
ng g c form
ng g s services/todos

Next, modify the following files:
app.component.html
// to include the new component tags
<div class="container">
<h1>{{appTitle}}</h1>
<app-todos [limit]="3"></app-todos>
<app-form></app-form>
</div>
...
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { TodosComponent } from './todos/todos.component';

// import httpclientmodule to be able to perform http requests
import { HttpClientModule } from '@angular/common/http';

// import forms module to be able to use 2-way [(binding)]
import { FormsModule } from '@angular/forms';
import { FormComponent } from './form/form.component';

@NgModule({
declarations: [
AppComponent,
TodosComponent,
FormComponent
],
// and place it (FormsModule) inside modules to be visible by all derived services and componenets
imports: [
BrowserModule,
HttpClientModule,
FormsModule //  add into imports the formsmodule to be accessible from the components
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

...
form.component.html

<div>
<input type="text" placeholder="Add todo..."
<!-- bind the title input field of the form to the model this.title -->
[(ngModel)]="title"
<!-- allow with enter to be able to run addTodo() -->
(keydown.enter)="addTodo()" />
<button (click)="addTodo()">Add todo</button>
</div>

...
form.component.ts

import { Component } from '@angular/core';
// import both the interface Todo as well as the TodoService
import { Todo, TodosService } from '../services/todos.service';

@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})

export class FormComponent implements OnInit {
// title will be bound to the title input of the form
title: string = '';

// use dependency injection to inject the TodosService
constructor(private todosService: TodosService) { }

addTodo() {
// construct todo object and add it using the service
const todo: Todo = {
id: Date.now(),
title: this.title,
complete: false,
}
this.todosService.addTodo(todo);
}
}
...


todos.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { TodosService, Todo } from '../services/todos.service';
import { Observable } from 'rxjs';

@Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
styleUrls: ['./todos.component.scss']
})
export class TodosComponent implements OnInit {

constructor(private todosService: TodosService) { }
private fetchData$: Observable<boolean>;
private todos$: Observable<Todo[]>;

// receive the limit from the parent(appcomponent) via input
@Input() limit: number;

ngOnInit() {
// initialize the both observables
this.fetchData$ = this.todosService.getTodos(this.limit);
this.todos$ = this.todosService.todos$;
}

// call the service
onChange(id: number) {
this.todosService.onToggle(id);
}

// call the service
removeTodo(id: number) {
this.todosService.removeTodo(id);
}

}

...

todos.component.html

<!-- mainly to fetch the http json data & setup this.todos -->
<div *ngIf="(fetchData$ | async ); else errorFetch"></div>

<!-- get the observable data and spread it on the page -->
<ul *ngIf="(todos$ | async ) as todos; else loading">
<li *ngFor="let todo of todos; let i = index">
<span [class.done]="todo.complete">
<input type="checkbox" [checked]="todo.complete" (change)="onChange(todo.id)">
<!--  bind [checked] to the state complete of todo
 pass todo.id on(change) event,
and run onChange function from the .ts file
-->
{{i + 1}} {{todo.title}}
</span>
<small>{{todo.date | date}}</small>
<!-- use pipe date to format the data: you can see other pipes from API/datepipe on angular website -->
<button class="remove" (click)="removeTodo(todo.id)">×</button>
</li>
</ul>

<ng-template #loading> Loading, please wait ...</ng-template>
<ng-template #errorFetch> There is an error while loading the data, please try again ...</ng-template>

...

todos.service.ts

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { tap, map, catchError } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

// create interface with the structure of a simple todo item
export interface Todo {
id: number
title: string
complete: boolean
}

@Injectable({
providedIn: 'root'
})
export class TodosService {

constructor(private http: HttpClient) { }
public todos: Todo[] = [];
private subject = new BehaviorSubject<Todo[]>([]); // to be able to retain the last emitted version !
public todos$ = this.subject.asObservable();

getTodos(limit: number): Observable<boolean> {

return this.http.get<Todo[]>(`https://jsonplaceholder.typicode.com/todos?_limit=${limit}`)
//  just return observable of true or false
.pipe(
map((fetchedTodos: Todo[]) => {
// setup the local this.todos member
this.todos = fetchedTodos;
// propagade the todos to subscribers via subject
this.subject.next(this.todos);
return true;
}),
catchError(err => {
alert(err.message);
return of(false);
})
);
}

onToggle(id: number) {
// get the todo index by the provided ID
const idx = this.todos.findIndex(todo => todo.id === id);
this.todos[idx].complete = !this.todos[idx].complete;
}

removeTodo(id: number) {
this.todos = this.todos.filter(todo => { return todo.id !== id }
);

// propagade the updated data back to all observables
this.subject.next(this.todos);
}

addTodo(todo: Todo) {
this.todos = [...this.todos, todo];
this.subject.next(this.todos);
}
}

Now you can run: ng serve
and browse: http://127.0.0.1:4200

Congratulations and enjoy learning Angular.

Sunday, December 15, 2019

Immutable JavaScript / ES6 to do list

Here is my interpretation of how to create a simple JavaScript ToDo list based on immutability and states. You may also enjoy the JavaScript for beginners - learn by doing course.

So far there are only add and delete actions created:
<input id="myInput" type="text" /><button id="Add">Add new Todo </button>
<div id="app"></div>

<script>
// state object
var state = {
name: 'todo list 1',
todos: [{ id: 1, data: 'first todo <button class="delete" data-id="1">X</button>' }]
};

//we pass the todo as object({with id, and the todo_info})
function applyAction(todo, action) {
switch (action) {
case 'add':
return [...state.todos, todo];
break
case 'remove':
//here remove it from index using todo.id
return state.todos
.filter(dbTodo => dbTodo.id !== todo.id);
break
case 'update':
// return [
// ...state.todos
// .filter(dbTodo => dbTodo.id !== todo.id), // first we remove the soon to be updated todo
// todo // then we add the updated version to the end of the array
// ];
return state.todos.map(
dbTodo =>
// whe check when we have match on id, we use todo, otherwise we leave the original dbTodo
dbTodo.id === todo.id ? todo : dbTodo
);
break
}
}


let renderTodos = todos => {
return `
<ul>${state.todos.map(todo => `
<li>${todo.data}</li>
`)} `;
}


// The state-based UI rendering
var render = state => {
var html = `
<h1>
${state.name}</h1>
${ state.todos.length < 1 ? 'Please add a todo' : ''} ${renderTodos(state.todos)} `; app.innerHTML = html; }; let setState = obj => {
// update the state with passed object
Object.keys(obj).forEach(key => {
state[key] = obj[key];
});
//update the render
render(state);
}

// Add todo event listener
document.querySelector('#Add').addEventListener('click', () => {
// get what's inside the input
const inputData = document.querySelector('#myInput');
if (inputData.value == '') return;
// just mapping into new array only the ids, then doing max on them
let lastId = (state.todos.length > 0) ? Math.max(...state.todos.map(todo => todo.id)) : 0;
current_todo = { id: ++lastId, data: `${inputData.value} <button class="delete" data-id="${lastId}">X</button>` }
setState({ //save the modified state and show the result
todos: applyAction(current_todo, 'add') // apply the add modification to the state
});
// clear the value inside the input
inputData.value = '';
});

// Remove todo event listener
document.querySelector('#app').addEventListener('click', (e) => {
if (e.target.classList.contains('delete')) {
const id = +e.target.getAttribute('data-id'); // convert the string data-id to number
const currentTodo = { id };
setState({ // save the modified state and show the result
todos: applyAction(currentTodo, 'remove') // apply the add modification to the state
});
}
});

// Render the initial UI
var app = document.querySelector('#app'); render(state);
</script>

Congratulations ! 

Resources:

JavaScript for beginners - learn by doing

Friday, December 06, 2019

Event delegation in JavaScript

In JavaScript we use event delegation for two main reasons:
1) to be able to process events coming from dynamically added page elements.
2) to achieve better application performance by listening to only one main element instead of having to add and remove event listeners each time a new element is dynamically added to the DOM.
More intriguing JavaScript aspects you can discover inside the JavaScript for beginners - learn by doing course.

 


In case of event delegation, when event occurs, we can filter out on which of the event generated source elements we would like to respond to using the event's target property: event.target
Here are two examples on how to use the event target:
// to filter out the processing of events coming from elements having different classes:
if (!event.target.classList.contains('my_class')) return;
// or to do filtering based on particular tag:
if (event.target.tagName == 'INPUT') {
// do our processing only if the source event came from an input tag
}

Example: Here we create an html with a simple ul/li list and add new LI button:
<button id="add">Add new LI</button>
<ul class="characters">
    <li>
        <input type="checkbox" data-index="0" id="child0">
        <label for="child0">Child 0</label>
    </li>
    <li>
        <input type="checkbox" data-index="1" id="child1">
        <label for="child1">Child 1</label>
    </li>
    <li>
        <input type="checkbox" data-index="2" id="child2">
        <label for="child2">Child 2 </label>
    </li>
</ul>
<script src="event_delegate.js"></script>

And here is our JavaScript code which adds new LI elements dynamically:
document.querySelector('#add').addEventListener('click', () => {
 // we create a new li element
let li = document.createElement('li');
// we get the last data-index attribute withing ul>li
    let dataId = characterList
        .lastElementChild // we use ElementChild instead of Child, because it ignores text and comment nodes
        .firstElementChild
        .getAttribute('data-index');
// increase the last id with 1
    ++dataId;
// construct the ne LI element with increased dataId
    li.innerHTML = `
    <input
    type="checkbox"
    data-index="${dataId}"
    id="child${dataId}">
    <label for="child${dataId}">Child ${dataId}</label>
    `;
// append the new LI eleemnt to the UL
    characterList.append(li);
});

// this is our little debug function showing up the event and where it is coming from
function toggle(event) {
    console.log('event: ' + event); //the event
    console.log('target: ' + event.target); //where occured the event
    console.log('currentTarget: ' + event.currentTarget); //attached parent element
    console.log('toggled element with id: ' + event.target.id);
}

// the actual parent element, where the event delegation is happening
const characterList = document.querySelector('.characters');
// here, instead of having multiple LI attached event listeners, we just attach 1 event listener to the UL list
characterList.addEventListener('click', toggle);

// Bonus: here is how to query specific element using its data-index attribute:
console.log(document.querySelector('input[data-index="1"]'));

Congratulations!

Resources:

JavaScript for beginners - learn by doing

Tuesday, November 26, 2019

One way data binding in JavaScript

Alright, since two-way data binding is often used in Angular, React and Vue and at the same time, not everyone likes it. I think that simple one-way data binding when writing JavaScript code is actually beneficial. More intriguing JavaScript aspects you can discover inside the JavaScript for beginners - learn by doing course.


What is one-way data binding: simply-said we would like the moment we change our data variable (model) this change to reflect immediately inside our generated HTML. Let's take a look at this example:
We have two span elements with two data bindings quote1 and quote2. You may consider them as just ids:
<span data-binding="quote1"></span>
<br>
<span data-binding="quote2"> </span>

Then we create the HTML change function (render) that will grab the element that is passed as a "property" parameter and will change its innerHTML based on what is inside the same property, taken from a special state object. This way effectively changing the HTML based on a passed parameter.

const render = property => {
document.querySelector(`[data-binding="${property}"]`).innerHTML = state.property;
};

Now to the main function setState. Its goal is to create a Proxy around a passed state obect, and whenever property of an object inside this state changes (i.e. user puts information inside (set)), the proxy will perform certain things, such as: setting the updated value inside the property, as well as using the previously defined render function to update the HTML view):

const setState = state => {
return new Proxy(state, {
set(target, property, value) { // detect if there are changes inside the property of a certain object
target.property = value; // updates the value of the property
render(property); // renders the HTML with the updated property to the screen
}
});
};

Now lets set an initial state:
const state = setState({
quote1: 'Initial quote state.'
});

Once it is set we can display the initial state on the console: console.log(state.quote1);

We can now try to modify some properties inside the Proxy state:
state.quote1 = 'quote1 new state';
state.quote2 = 'quote2 new state';
and see how their updated changes are reflected on the browser HTML.

You now may use the Proxy design pattern for your projects. 

Congratulations!

Resources:

JavaScript for beginners - learn by doing

Sunday, November 24, 2019

Observer and subscriptions in JavaScript

Observers are widely used in JavaScript frameworks such as Angular, libraries like RxJS and others. That is why it is good to know what they do under the hood. The same principle is being successfully utilized by the JavaScript event listeners. More of the intriguing JavaScript aspects you can discover inside the JavaScript for beginners - learn by doing course.

In general, we have a list of subscribers that subscribe to certain events. When certain event emits data, all of its connected subscribers receive the data. The role of an observer is to observe and distribute events to proper subscribers. The simple action of subscribing to an event is referred to as a subscription.


Now to the code:

// we create a class: Observer
class Observer {
inside of its constructor we set up an initially empty list of subscribers.
constructor() {
this.subscribers = [];
}

// function subscribe, just adds subscriber to the subscribers' list
subscribe(subscriber) {
this.subscribers.push(subscriber);
}


// optional: unsubscribe from a particular event, we just remove a subscriber from the subscribers' array. Note that we need to pass as a parameter the whole subscriber object structure containing event and action, to be compared with the existing subscribers inside the this.subscribers array. This is because we would like to distinguish between multiple subscribers for the same event.
unsubscribe(subscriber) {
this.subscribers = this.subscribers.filter(subscriber => subscriber !== subscriber);
}


// inside the publish function, we do several things
publish(event, data) {
 // we search for those subscribers which are subscribed and respond to the same event as the passed function parameter (event)
this.subscribers
.filter(
subscriber =>
subscriber.event === event
)
// then for each list of the found subscribers we propagadate the data parameter i.e. send information to them
.forEach(
subscriber =>
subscriber.action(data)
);
}

}

Now let's see the observer usage in action:

// we first construct object out of our observer class: my_observer
const my_observer = new Observer();

// then we subscribe to event 1, we actually set up the event we will be listening to, as well as what kind of action to be performed when the event is received
my_observer.subscribe({
event: 'event 1',
action: (data) => {
console.log('received event 1', data);
}
});

// now to subscribe to another event
my_observer.subscribe({
event: 'event 2',
action: (data) => {
console.log('received event 2', data);
}
});

// when we have the subscriptions, we will fire up 2 events to event 1, using a delay via setTimeout()
setTimeout(() => {
my_observer.publish('event 1', 'Sending data to event 1 listeners/subscribers');
}, 1500);

// and lets fire up one event to the event 1 subscriber
setTimeout(() => {
my_observer.publish('event 1', 'Sending data to event 2 listeners');
}, 2500);

We should be able to see the 3 events received and being responded to.
Please test the code to see the observers working in action.

Congratulations!

Resources:

JavaScript for beginners - learn by doing

Saturday, November 23, 2019

Store state management with RXJS BehaviorSubject

Basically, we will be creating and using an observer with the help of RXJS library. If you would like to see more of the RXJS in action you can take a look at this Angular course.


It is important to notice that the store will keep the immutable state principle. Which means that all the operations we are about to do on the stored objects inside will not modify the store itself, but will instead produce and return a new store.
Using immutability brings benefits such as preventing data race conditions as well as working with a different from the latest state of the same object when several observers are interacting with it.

For the demonstration, we will create 2 HTML buttons, which when triggered will increment or decrement the values of #inc element data
<button id="inc">+</button> <button id="dec">-</button>

<span id="state"></span>

#state will respond to changes inside of our observed data.

Then, to use behavioursubject we will import it from the rxjs library. It has the ability to save the last emitted value inside itself (thus imitating state) and when a subscriber asks for the saved data (subscribes to the behavior subject) the behavior subject will emit it.

import { BehaviorSubject } from "rxjs";

// we create a class Store with initial state object which has data inside( name and votes). All this initialization is done inside the constructor of the class.
class Store {
  constructor() {
    this.initialState = {
      data: [
        {
          name: "initial name",
          votes: 0
        }
      ]
    };

// we create a new behavior subject and load up the initialState data inside.
    this.subject$ = new BehaviorSubject(this.initialState);

// we create an observable from the subject, in order to be able to access the data from the behavior subject as read-only (i.e not to be able to write and populate values to other subscribers, thus creating chaos inside the data logic).
    this.state$ = this.subject$.asObservable();
  }

// We then use two functions (getters and setters, which get and set the state inside the behavior subject. (with .next() we emit values to the subject, .getValue() is not used very often, and here is just to get the current value inside the subject)
)

  get state() {
    return this.subject$.getValue();
  }
  setState(nextState) { // the function can be also defined as: set state()
    this.subject$.next(nextState);
  }
}

// now it is time to create an object from our previously defined class Store();
const store = new Store();

// When we click on the + button we set a new state inside of our store.

// Note that we have data and state, so each state is characterized with its own data. Here we just modify the initial(old) state with our data object {name, votes}.
document.querySelector("#inc").onclick = function() {
  store.setState(
    //add new objects
    {
      ...store.state,
      data: [...store.state.data, { name: "initial name", votes: 0 }]
    }
  );
};

Searching/querying objects inside the store. In our store, we can also have objects with different names. Here is how to do it: we start by attaching an event handler to the click on the + button:

document.querySelector("#inc").onclick = function() {
// this time we can search for a particular named object, we are interested in inside the store
let searched_name = "initial name";
  store.setState(
    {
      ...store.state,
      data : store.state.data.map(store_data => { // we loop through the whole store
        // console.log('data inside the store: '+JSON.stringify(inner_data));
        if (store_data.name === searched_name) {

// and if we have a match we change the corresponding store object by incrementing its current votes property with 1

          return {...store_data, votes: store_data.votes + 1 };
        }
        return store_data; // after the whole mapping logic, we return the modified 'stored_data' variable to be a property of the 'data' variable.
      })
    }
  );

Note: inside setState we return not a modified old store.set object, but a newly created object so keeping the immutability rule true.

// Here is how to delete objects based on searched_name variable. We loop throughout all the store data, compare and return only the objects which are not equal to the deleted value. Once again keep an eye on the immutability principle, that we are returning a completely new object and don't modify the store.state object by deleting its entries.
document.querySelector("#dec").onclick = function() {

 store.setState(
{
      ...store.state,
      data : store.state.data.filter(store_data => {
    return    store_data.name !== searched_name
      })
    }
  );
};
});

Last but not least if we want our HTML to reflect on the changes from the store we subscribe to BehaviorSubject:
store.state$.subscribe(state => {
// we can either display the current state to the console
  console.log("current state: " + JSON.stringify(state));
// or just place it inside our #state span element
document.querySelector('#state').innerHTML = state.votes;
});

...

Here is an alternative version, which achieves a similar functionality:

//initially we set out empty state object {}:
let initialState = {};

class AppState {
// we again create our behavior subject to store the empty initial state
  private stateSubject = new BehaviorSubject(initialState);
//then we create an observable out of the subject
  state$ = this.stateSubject.asObservable().pipe(
    scan((acc, newVal) => { // on every new value that the subject receives, via the scan function, we get it, together with its previous value acc (scan() in rxjs() behaves like reduce in javascript)
      return { ...acc, ...newVal }; // we create a new object consisting of the old accumulator value plus the newValue changes applied
    })
  );

// here is the important dispatch function, which simply via .next() places a new payload into a specific object key, thus ensuring that the particular object will have its new state set.
  dispatch(obj) {
    this.stateSubject.next(
      { [obj.key]: obj.payload }
    );
  }

}

// optionally we can debug the state inside the observable using Angular's async and json pipes:
{{ appState.state$ | async | json }}

// Here is how we can use the dispatch method: we just send new data to a specific key of our state.
this.appState.dispatch({
      key: 'person',
      payload: {
        name: '',
        website: ''
      }
    });

Congratulations and enjoy the Angular course!

Friday, September 13, 2019

Install and configure Webpack

Why use Webpack?
part of the course: JavaScript for beginners - learn by doing

  • to auto minimize, compile and transpile your modern ES6 and above version of JavaScript
  • to auto-reload your browser
  • to auto-include and merge external JavaScript files into one
  • and many more...
In this tutorial, we will focus on those three. First, download and install NodeJS, because we will need npm (node package manager) included in the NodeJS installation.
If you would like you can watch the video:


Prerequisites:
Have two files: index.html and main.js Inside index.html include index.html include
Inside main.js you can place any JavaScript code.

We will start by creating our first project. Why? - again to be able to have a portable version of our code which can other developers run in their machines. The project information will be stored inside the project.json file.
just type: npm init and follow the questions. Next, we will install webpack with:
npm install --save-dev webpack
Open project.json and you will see section devDependencies with the webpack inside. All upcoming package installations or so-called dependencies will have their place in our package.json file. As you can see --save-dev option is just installing the packages for development mode in the devDependencies section -  this means that we can have production and development dependencies and they can differ - which is nice because you would like to include in your production/live application only the needed libraries.
You can also see now that there is directory /node_modules/ - well there all downloaded and installed packages and their dependent packages will be placed.
Upon compilation / transpiling only part of those (certain functions) will be used and have their place in the final development or production project.
By the way, if other users have your files (excluding /node_modules/ which is heavy in KB) they just need to run npm install and they will have all the modules installed automatically based on your package.json file.
We need to install webpack-cli. Please do so with npm
And now we need to modify the "scripts" section of package.json to:
"scripts":{
    "build":"webpack"
}
Then just type npm run build and this will start webpack.
Create folder: /src and place inside index.html and main.js.
Inside package.json replace main.js to index.js.
Rename main.js as index.js ->this is needed as an entry point index.js
Now run again: npm run build and you will see a new directory: /dist This is newly created by webpack and is where the production code resides. So you can browse it via: http://project_directory/dist directly in your browser.

Next, we need a plugin that will minify and load/import directly our JavaScript into the HTML. We will use: html-webpack-plugin and html-loader. Do npm install them!
Now is time to create the webpack.config.js file with the following content:

const htmlplugin = require("html-webpack-plugin");
module.exports = {
module: {
 rules: [
{
   test:/\.html$/,
   use:[  { loader:"html-loader",   options: {minimize:true} }  ]
}
]
},
plugins:[
    new htmlplugin(       {  template:"./src/index.html",filename:"./index.html"} )
]
}
Then npm run build.
And you can see that index.html is modified (inside the /dist folder). From /src/index.html you can now remove the line (npm will auto-include it for us).
You can test the code from /dist directory - now everything works !

Lets apply Babel to be able to use ES6 on a wide range of browsers !
npm i --save-dev babel-loader @babel.core @babel/preset-env
create .babelrc file and place there:
{
 "presets":[ "@babel/present-env" ]
}
Now add babel to webpack.config.js
{
 test: /\.js$/, exclude:/node_modules/,
 use[{loader:"babel-loader"}]
}

Type again npm build and you can see that the project now transpiles ES6 into ES5 !

Let's do some live code reloading:
npm i --save-dev webpack-dev-server
then in package.json:
place new line inside of scripts:
"dev":"webpack-dev-server"
Now: npm run dev
Load up your website and then modify something inside of your JavaScript file.
You will see how the browser auto-reloads and you can see your changes!

Congratulations and enjoy learning!

Monday, July 15, 2013

Mark visited links using JavaScript and localStorage

The following code will analyze each and every link on your webpage in a way so when a user clicks on a link it will store its location in browser's localStorage. The code first loops over the links, placing event handlers on their click event. Then when a user clicks on a link it checks for its existence in the local-storage array and if not pushes it there. At the same time adds the class 'visited' to all visited links found in the array.
If you would like to understand more of its internal functionality, I would suggest taking a look at the comprehensive course: JavaScript for beginners - learn by doing

function check_visited_links(){
var visited_links = JSON.parse(localStorage.getItem('visited_links')) || [];
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
    var that = links[i];
    that.onclick = function () {
        var clicked_url = this.href;
        if (visited_links.indexOf(clicked_url)==-1) {
            visited_links.push(clicked_url);
            localStorage.setItem('visited_links', JSON.stringify(visited_links));
        }
    }
    if (visited_links.indexOf(that.href)!== -1) { 
        that.parentNode.className += ' visited';
    }
}
}

if you want to add some styling just add the following CSS rule:

.visited{
color:silver
}

Monday, August 25, 2008

AJAX & PHP star rating system script

Here is how to make a star rating script for your webpages.
The new and updated version of the script is now tracking all of your web pages automatically. You just have to include it on the desired page.
A new and improved version of the script, along with its explanation can be found in the star rating script course.

if you don't have a database you can create it with first with:

CREATE DATABASE ratings;

then enter the database with :

use database ratings;


and lets set up the MySQL table:


CREATE TABLE IF NOT EXISTS `ratings` (

  `id` varchar(255) NOT NULL,

  `total_votes` int(11) NOT NULL,

  `total_value` int(11) NOT NULL,

  `used_ips` longtext,

  PRIMARY KEY (`id`)

);


Then copy and paste this php code into ratings.php and open the file for edit:

 var http_request = false;  
 function alertContents(response, ret_el) {  
   document.getElementById(ret_el).innerHTML = response;  
 }  
 function makePOSTRequest(url, parameters, ret_el, callback_function) {  
   if (typeof callback_function == 'undefined') callback_function = alertContents;  
   var http_request = false;  
   var activex_ids = ['MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];  
   if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+...  
     http_request = new XMLHttpRequest();  
     if (http_request.overrideMimeType) {  
       http_request.overrideMimeType('text/xml');  
     }  
   } else if (window.ActiveXObject) { // IE6 and older  
     for (i = 0; i < activex_ids.length; i++) {  
       try {  
         http_request = new ActiveXObject(activex_ids[i]);  
       } catch (e) {}  
     }  
   }  
   if (!http_request) {  
     alert('Please update your browser!');  
     return false;  
   }  
   document.getElementById(ret_el).innerHTML = "Please wait...";  
   document.getElementsByTagName("body").item(0).style.cursor = "wait";  
   http_request.onreadystatechange = function() {  
     if (http_request.readyState !== 4) {  
       return;  
     }  
     if (http_request.status !== 200) {  
       alert('Please try again later.');  
       return;  
     }  
     document.getElementsByTagName("body").item(0).style.cursor = "auto";  
     var response = http_request.responseText;  
     callback_function(response, ret_el);  
     return;  
   };  
   http_request.open('POST', url, true);  
   http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
   // http_request.setRequestHeader("Charset", "windows-1251");  
   http_request.setRequestHeader("Content-length", parameters.length);  
   http_request.setRequestHeader("Connection", "close");  
   http_request.send(parameters);  
 }  
 function rate(url_id, vote) {  
   makePOSTRequest('star_rating.php', 'url_id=' + url_id + '&vote=' + vote, 'myspan');  
 }  
 window.onload = function() {  
   var percentstyle = '',  
     rate_percent = 0;  
   var current_rating = document.getElementById("current_rating");  
   if (current_rating) {  
     rate_id = current_rating.getAttribute('data-id');  
     rate_percent = current_rating.getAttribute('data-percent');  
   }  
   percentstyle = 'width:' + rate_percent + 'px;';  
   var content = '<div class="rating" id="rating"><ul class="star-rating"><li class="current-rating" style="' + percentstyle + '" >Current rating</li><li><a id="rate1" class="one-star">1</a></li><li><a id="rate2" class="two-stars">2</a></li><li><a id="rate3" class="three-stars">3</a></li><li><a id="rate4" class="four-stars">4</a></li><li><a id="rate5" class="five-stars">5</a></li></ul></div>';  
   if (document.getElementById("myspan")) {  
     document.getElementById("myspan").innerHTML = content;  
   }  
   var url_id = encodeURIComponent(rate_id);  
   for (var i = 1; i < 6; i++) {  
     (function(i) {  
       document.getElementById("rate" + i).addEventListener("click", function() {  
         rate(url_id, i);  
         return false;  
       });  
     })(i);  
   }  
 }  


<?php  
 $dbhost = 'localhost';  
 $dbuser = '';  
 $dbpass = '';  
 $dbname = 'ratings';  
 $dbtable = "ratings";  
 $conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname) or die('Error connecting to mysql');  
 $is_voting = isset($_POST['vote']) ? $_POST['vote'] : ''; //the actual user vote  
 if ($is_voting) {  
   $id = isset($_POST['url_id']) ? $_POST['url_id'] : ''; //passed url_id   
 } else {  
   $id = substr($_SERVER['REQUEST_URI'], 1);  
   $id = htmlentities(urlencode($id), ENT_QUOTES);  
 }  
 //make initial vote check  
 $sql = "SELECT total_votes, total_value, used_ips FROM $dbtable WHERE id = '$id' ";  
 $query = mysqli_query($conn, $sql) or die(" Error: " . mysqli_error());  
 $number_rows  = mysqli_num_rows($query);  
 $numbers    = mysqli_fetch_assoc($query);  
 $checkIP    = unserialize($numbers['used_ips']);  
 $count     = $numbers['total_votes']; //how many votes total  
 $current_rating = $numbers['total_value']; //total number of rating added together and stored  
 $sum      = $is_voting + $current_rating; // add together the current vote value and the total vote value  
 $tense     = ($count == 1) ? "vote" : "votes"; //plural form votes/vote  
 if (!$is_voting) {  
   //check if have voted already  
   $voted = mysqli_fetch_assoc(mysqli_query($conn, "SELECT * FROM $dbtable WHERE used_ips LIKE '%" . $_SERVER['REMOTE_ADDR'] . "%' AND id='$id' ")); //This variable searches through the previous ip addresses that have voted and returns true or false  
   //when already voted  
   if ($voted) {  
     echo '<ul class="star-rating"><li class="current-rating" style="width:' . @number_format($current_rating / $count, 2) * 25 . '%;"></li></ul>  
 Rating: <strong>' . @number_format($current_rating / $count, 2) . '</strong> ( ' . $count . $tense . ')  
 <br /><span style="color:red;">You have already voted.</span><br />';  
   } else {  
     //if not voting just show the current rating  
     //set data for the css  
     echo '<div id="myspan"><span id="current_rating" data-id="' . $id . '" data-percent="' . @number_format($current_rating / $count, 2) * 25 . '"></span></div>';  
   }  
 }  
 //if not voted do the actual voting  
 if ($is_voting) {  
   //open initial voting row if necessary  
   if ($number_rows == 0) {  
     $sql = "INSERT INTO $dbtable (id, total_votes, total_value, used_ips) VALUES ('$id', '0', '0', '')";  
     $result = mysqli_query($conn, $sql) or die("err");  
   }  
   //increment votes, check ips & add/update vote to table  
   if ($sum == 0) {  
     $added = 0;  
   } else {  
     $added = $count + 1;  
   }  
   if (is_array($checkIP)) {  
     array_push($checkIP, $_SERVER['REMOTE_ADDR']);  
   } else {  
     $checkIP = array(  
       $_SERVER['REMOTE_ADDR']  
     );  
   }  
   $insert = serialize($checkIP);  
   mysqli_query($conn, "UPDATE $dbtable SET total_votes='$added', total_value='$sum', used_ips='$insert' WHERE id='$id'") or die("Error");  
   echo $response = '<div class="rating">Your rating:' . $is_voting . ' <br />';  
   echo '<ul class="star-rating"><li class="current-rating" style="width:' . @number_format($sum / $added, 2) * 25 . '%;"></li></ul>';  
   echo 'Overall rating: <strong>' . @number_format($sum / $added, 2) . '</strong> <br />   
   <span style="color:red;">Thank you for your vote cast!</span></div>';  
   //echo iconv("windows-1251", "UTF-8", $response);  
 }  
 ?>  


You can also explore the full version of the script in the course.

Enjoy!

Subscribe To My Channel for updates

Integrating AI code helpers into Visual Studio Code

In this guide, we’ll walk through setting up a local AI-powered coding assistant within Visual Studio Code (VS Code). By leveraging tools s...