0

Hi guys I'm trying to create a display toggler, I got 2 pages index.html and toggler.html, index.html displays the "TEXT" and toggler.html contains the TOGGLER or SWITCH BUTTON. If the TOGGLER is SWITCH ON, TEXT will be displayed in index.html else it will disappear.

Requirements:

  1. text will display and disappear in REAL TIME.
  2. text stays in the page as long as toggler is ON even if you refresh the index.html page.
  3. the TOGGLER's default value (OFF) will reset after midnight (new date).

Any recommendation how to achieve this reliably with very minimal code.

enter image description here

C4DXYZ-SO
  • 21
  • 9
  • Are `index.html` and `switch.html` opened on the same computer, in the same browser, from the same domain? And what have you tried? This is not a site to ask for recommendation, it's to solve programming problems. Yours is not really a problem. – KIKO Software Nov 14 '21 at 11:52
  • I can build it dirty that's why I need idea as clean head start. – C4DXYZ-SO Nov 14 '21 at 12:02
  • If all requirements can be met then cookies and js it is. Do I need a listener to monitor the toggler status? – C4DXYZ-SO Nov 14 '21 at 12:11
  • Let me just clarify my first comment, it can be opened in any computer/browser. But YES from the same domain. – C4DXYZ-SO Nov 14 '21 at 12:19
  • Yes, I would probably use an event listener, but other solutions are possible. I'm sorry, but your clarification doesn't clarify much. Any HTML file can be opened in any computer/browser, we know that. My question was: Is it always on the same computer, browser and domain? Only in that specific case can you use cookies and Javascript. I had to delete my second comment because you deleted your first. – KIKO Software Nov 14 '21 at 12:26
  • DB was the best choice for this, but as u said u are not using DB, then use sessions, u can maintain session for every user `$_SESSION['switch']` customize your session for long use. – Mohammed Khurram Nov 14 '21 at 12:34
  • Sorry I'm also confused. Yes it always opened on the same computer/browser BUT user should also be able to open it from different computer/browser not limited to just one computer/browser that's what I meant. – C4DXYZ-SO Nov 14 '21 at 12:43
  • According to my research the entire requirements can't be met without DB because this requires a GLOBAL value that can be fetch from any computer/browser. So I'll edit the requirements and REMOVE the 4th requirement. – C4DXYZ-SO Nov 14 '21 at 13:47
  • @MohammedKhurram Sir what's your suggestion I already remove the 4th requirement because it seems impossible to accomplish considering that the switch.html should be accessible to any computer/browsers. – C4DXYZ-SO Nov 14 '21 at 13:53
  • ok @C4DXYZ-SO then u can use DB to store user the switch value, then `if($row['switch'] == "true") add text to index.html otherwise no. If you build it this way, you don't need to worry about switch to keep on/off anywhere, for any user, just implement using DB. – Mohammed Khurram Nov 14 '21 at 14:28
  • What have you tried so far? Where are you stuck? – Nico Haase Nov 25 '21 at 08:45

1 Answers1

1

Websocket

In this example Ratched

Install via Composer: composer require cboden/ratchet

socket.php

<?php
require __DIR__ . '/vendor/autoload.php';

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

class Socket implements MessageComponentInterface {
    protected $clients;
    private $state = 0;
    private $date;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
        $this->date = date("G");//Saves the date the day the socket was started
    }

    public function dateListener(){
        $d = date("G");
        if($d < $this->date){//check if day has changed
            $this->state = 0;
            $this->date = $d;
            foreach($this->clients as $client){//resetting the state to default(0)
                $client->send($this->state);
            }
        }
    }

    public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later
        
        $this->clients->attach($conn);
        foreach($this->clients as $client){
            //if a new client connects it gets the current state
            $client->send($this->state);
        }

        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        $numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // The sender is not the receiver, send to each client connected
                if($msg == 1){//changing state and sending it to all clients
                    $this->state = 1;
                    $client->send($this->state);
                }else if($msg == 0){
                    $this->state = 0;
                    $client->send($this->state);
                }
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        // The connection is closed, remove it, as we can no longer send it messages
        $this->clients->detach($conn);

        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";

        $conn->close();
    }
}

    // Run the server application through the WebSocket protocol on port 8080
    $sckt = new Socket();
    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                $sckt
            )
        ),
        8080
    );
    //to check continuously if the date has changed
    $server->loop->addPeriodicTimer(5, function () use ($sckt) {        
        echo $sckt->dateListener();
    });


    $server->run();

index.html

    <!DOCTYPE html>
<html>
    <head>
        <title>Index</title>
    </head>
    <body>
        <div id="text"></div>
    </body>
    <script>
        var conn = new WebSocket('ws://localhost:8080');//connect
        conn.onopen = function(e) {
            console.log("Connection established!");
        };

        conn.onmessage = function(e) {//receive current state
            var div = document.getElementById("text");
            if (e.data == 1){
                div.innerHTML = "text";//your text
            }else if (e.data == 0){
                div.innerHTML = "";
            }
            
            
        };

    </script>
</html>

switch.html

    <!DOCTYPE html>
<html>

<head>
    <title>switch</title>
</head>

<body>
    <button onclick="sendToggle()">Toggle</button>
</body>
<script>
    var conn = new WebSocket('ws://localhost:8080');//connect
    var toggle = 1;
    conn.onopen = function (e) {
        console.log("Connection established!");
    };

    conn.onmessage = function (e) {//receive current state
        if(e.data == 1){
            toggle = 1;
        }else if(e.data == 0){
            toggle = 0
        }
    };


    this.send = function (message, callback) {
        this.waitForConnection(function () {
            conn.send(message);
            if (typeof callback !== 'undefined') {
                callback();
            }
        }, 1000);
    };

    this.waitForConnection = function (callback, interval) {
        if (conn.readyState === 1) {
            callback();
        } else {
            var that = this;
            // optional: implement backoff for interval here
            setTimeout(function () {
                that.waitForConnection(callback, interval);
            }, interval);
        }
    };

    function sendToggle() {//send new state by pressing the button
        if (toggle == 1) {
            this.send("0",function(){
                console.log("sent");
                toggle = 0;
            });
        } else {
            this.send("1",function(){
                console.log("sent");
                toggle = 1;
            });
        }
    }


</script>

</html>

To start the websocket simply run the following command php socket.php

To deploy the this version you need ssh access to your sever to run socket.php. So that the execution of the file does not stop when you close the terminal window you need to create a screen session.

To install screen on a linux server do the following:

On Ubuntu or Debian

sudo apt-get install screen

On CentOS and Fedora

sudo yum install screen

To start a screen session type screen. After starting the session navigate to your websocket.php and run it. To detach from a session simply type Ctrl+a d


Edit

Using long pulling is also an option. this removes the need of ssh access.

I am using ajax to get and change the current state by making requests to the server

Ajax and php docs

Create the following file structure:

-client.js
-index.html
-switch.html
-switch.js
--/server
  -changeState.php
  -data.txt
  -server.php

Extends this

index.html

<html>
    <head>
        <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
        <script type="text/javascript" src="client.js"></script>
    </head>
    <body>
        <h1>Response from server:</h1>
        <div id="response"></div>
    </body>
</html>

switch.html

<html>
    <head>
        <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
        <script type="text/javascript" src="switch.js"></script>
    </head>
    <body>
        <button id="toggler" state="" onclick="toggleState()">Toggle</button>
        <script>
            toggleState();//to get current state
        </script>
    </body>
</html>

Change the url parameter in both ajax requests to deploy it.

client.js

/**
 * AJAX long-polling
 *
 * 1. sends a request to the server (without a timestamp parameter)
 * 2. waits for an answer from server.php (which can take forever)
 * 3. if server.php responds (whenever), put data_from_file into #response
 * 4. and call the function again
 *
 * @param timestamp
 */
 function getContent(timestamp)
 {
     var queryString = {'timestamp' : timestamp};
 
     $.ajax(
         {
             type: 'GET',
             url: 'http://localhost:80/longPulling/server/server.php',
             data: queryString,
             success: function(data){
                 // put result data into "obj"
                 console.log(data);
                 var obj = jQuery.parseJSON(data);
                 // put the data_from_file into #response
                 $('#response').html(obj.data_from_file);
                 // call the function again, this time with the timestamp we just got from server.php
                 getContent(obj.timestamp);
             }
         }
     );
 }
 
 // initialize jQuery
 $(function() {
     getContent();
 });

switch.js

function toggleState()
 {
     var currentState = $("#toggler").attr("state")
     console.log("currentState: "+currentState);
     var queryString = {};
     if(currentState == ""){
        queryString = {'state' : 0};//default state
     }else{
        queryString = {'state' : currentState}
     }
 
     $.ajax(
         {
             type: 'GET',
             url: 'http://localhost:80/longPulling/server/changeState.php',
             data: queryString,
             success: function(data){
                 // put result data into "obj"
                 var obj = jQuery.parseJSON(data);
                 $("#toggler").attr("state",obj.state);
             }
         }
     );
 }
 
 // initialize jQuery
 $(function() {
     toggleState();
 });

provides the current state

server/server.php

<?php

/**
 * Server-side file.
 * This file is an infinitive loop. Seriously.
 * It gets the file data.txt's last-changed timestamp, checks if this is larger than the timestamp of the
 * AJAX-submitted timestamp (time of last ajax request), and if so, it sends back a JSON with the data from
 * data.txt (and a timestamp). If not, it waits for one seconds and then start the next while step.
 *
 * Note: This returns a JSON, containing the content of data.txt and the timestamp of the last data.txt change.
 * This timestamp is used by the client's JavaScript for the next request, so THIS server-side script here only
 * serves new content after the last file change. Sounds weird, but try it out, you'll get into it really fast!
 */

// set php runtime to unlimited
set_time_limit(0);

// where does the data come from ? In real world this would be a SQL query or something
$data_source_file = 'data.txt';
// If your using a database your table needs to following columns
// ID   state   createdAt
// 
// $sql = "SELECT * FROM mytable ORDER BY ID DESC LIMIT 1"
// $servername = "localhost";
// $username = "username";
// $password = "password";
// $dbName = "myDataBase"

// // Create connection
// $conn = new mysqli($servername, $username, $password, $dbName);

// // Check connection
// if ($conn->connect_error) {
//   die("Connection failed: " . $conn->connect_error);
// }
//
// $result = $conn->query($sql);
// $row = $result->fetch_assoc();
// $lastChanged = $row['created_at'];
// $currentState = $row['state'];


// main loop
while (true) {

    // if ajax request has send a timestamp, then $last_ajax_call = timestamp, else $last_ajax_call = null
    $last_ajax_call = isset($_GET['timestamp']) ? (int)$_GET['timestamp'] : null;

    // PHP caches file data, like requesting the size of a file, by default. clearstatcache() clears that cache
    clearstatcache();
    // get timestamp of when file has been changed the last time
    // use $lastChanged if using database
    $last_change_in_data_file = filemtime($data_source_file);

    // if no timestamp delivered via ajax or data.txt has been changed SINCE last ajax timestamp
    if ($last_ajax_call == null || $last_change_in_data_file > $last_ajax_call) {

        // get content of data.txt
        // use $state if using database
        $data = file_get_contents($data_source_file);

        // put data.txt's content and timestamp of last data.txt change into array
        $result = array(
            'data_from_file' => $data,
            'timestamp' => $last_change_in_data_file
        );

        // encode to JSON, render the result (for AJAX)
        $json = json_encode($result);
        echo $json;

        // leave this loop step
        break;

    } else {
        // wait for 1 sec (not very sexy as this blocks the PHP/Apache process, but that's how it goes)
        sleep( 1 );
        continue;
    }
}

changes the current state

server/changeState.php

 <?php
//copy database connection from server.php
if(isset($_GET['state']) && ($_GET['state'] == 1 || $_GET['state'] == 0)){
    $newState = 0;
    if($_GET['state'] == 0){
        $newState = 1;
    }
    // $sql = "INSERT INTO mytable (state) VALUES ($newState);"
    // if ($conn->query($sql) === TRUE) {
    //     echo "New record created successfully";
    //   } else {
    //     echo "Error: " . $sql . "<br>" . $conn->error;
    //   }
    file_put_contents("data.txt", $newState);
    $response = array(
        'state' => $newState
    );
    $json = json_encode($response);
    echo $json;
}else{
    //look into server.php how to get data out of database
    $content = trim(file_get_contents("data.txt"));
    if($content == 1 || $content == 0){
        $response = array(
            'state' => $content
        );
        $json = json_encode($response);
        echo $json;
    }else{
        //copy content insertion from above
        file_put_contents("data.txt", 0);
        $response = array(
            'state' => 0
        );
        $json = json_encode($response);
        echo $json;
    }

}

stores the current state server/data.txt

0

You should create the file structure locally and then upload the folder to your server.

It's not very sexy but it works.

noah1400
  • 1,282
  • 1
  • 4
  • 15
  • Thanks for the response I'm gonna test your code. I hope it mets all the requirements. – C4DXYZ-SO Nov 14 '21 at 17:36
  • Kindly explain the purpose of validation to socket.php file so I'll be able to assess if it's really necessary for my case. – C4DXYZ-SO Nov 14 '21 at 17:39
  • @C4DXYZ-SO I edited my answer a bit. Now it should meet all the requirements – noah1400 Nov 14 '21 at 19:45
  • Does this require COMPOSER to be installed locally? Because I don't have physical/remote access to that computer I only have access to the backend of the website . – C4DXYZ-SO Nov 15 '21 at 04:26
  • BUT I did create a DB to hold the GLOBAL value (state of the switch). – C4DXYZ-SO Nov 15 '21 at 04:28
  • @C4DXYZ-SO Are you able to upload files to your server/computer? – noah1400 Nov 15 '21 at 08:42
  • I made some progress with my code BUT the problem is the REAL TIME thing and the after midnight RESET TO DEFAULT state. I installed NODE JS, COMPOSER with RATCHET but I ran into connection error when I tested your code @ //localhost/8080, seems like I can't establish connection, what did I miss @noah1400? – C4DXYZ-SO Nov 21 '21 at 13:34
  • should there be a need to change the port OR is there some permission that I need to configure somewhere? I am just testing it via localhost at the moment. – C4DXYZ-SO Nov 21 '21 at 13:37
  • on the other hand, don't you think websocket seems a little bit OVERKILL for this thing? I made some research and it seems like the technique called LONG POLLING is the more suitable for my situation. What do you think? @noah1400 – C4DXYZ-SO Nov 21 '21 at 13:43
  • I got it. I failed to run the socket.php. BUT what if I upload it to a LIVE PRODUCTION, do I need to create a trigger to LAUNCH or STOP the socket.php or should I just leave it running all the time? – C4DXYZ-SO Nov 21 '21 at 14:38
  • OR should I just INCLUDE it in index.php? – C4DXYZ-SO Nov 21 '21 at 14:39
  • @C4DXYZ-SO In the question it said index.HTML and switch.HTML so I thought you wanted to use html files. But in theory it should work if you include it in index.php, but then other problems arise like that you always have to start index.php first before switch.php(or html) or that only one user can access the page at a time. – noah1400 Nov 22 '21 at 14:16
  • Even if you use long pulling you need to run a php file on your server – noah1400 Nov 22 '21 at 14:56
  • I already resolve the error, I just failed to run socket.php in the command line. – C4DXYZ-SO Nov 22 '21 at 14:59
  • My real problem here is not running this locally but when I deploy this to the websites domain, how can I trigger the socket.php to run, let say I will use index.html? – C4DXYZ-SO Nov 22 '21 at 15:02
  • yes it's working via localhost – C4DXYZ-SO Nov 22 '21 at 15:02
  • @C4DXYZ-SO and you are not able to connect to the server via ssh? – noah1400 Nov 22 '21 at 15:41
  • @C4DXYZ-SO It took a bit of time but I edited my answer. Now there should be an Option that works for your (long pulling). The only thing you need to do is implement the reset after midnight – noah1400 Nov 24 '21 at 13:36
  • I'm gonna give this a try. Your first code works well locally BUT I am having trouble deploying it to the LIVE HOST and making it work because the documentation is not that simple to follow. I'm doing research BUT the informations I got is just too complex for my level. – C4DXYZ-SO Nov 25 '21 at 00:41
  • @C4DXYZ-SO I added some Information to my answer. To deploy the first version you need ssh access to your server. You said you don't have remote access to your computer that means the first version doesn't work for you. Using the second version removes the need of an ssh connection. Simply upload the file structure as I described in my answer – noah1400 Nov 25 '21 at 09:18
  • If I'm going to use checkbox as toggler instead of button is $("#toggler").attr("state") going to be equivalent to $('#myCheckBox').each(function(){ this.checked = false; });? I think I'm gonna need several revision since I already have a working DB instead of using the data.txt – C4DXYZ-SO Nov 25 '21 at 11:03
  • using `.each` is not necessary because `$('#myCheckBox')` will return only one element. If you have multiple elements set the `class` attribute to `checkBoxClass` and use `$('.checkBoxClass').prop('checked',false);`. – noah1400 Nov 25 '21 at 13:51
  • Added comments for sql. Read the following for information about sql and php created_at : https://stackoverflow.com/questions/267658/having-both-a-created-and-last-updated-timestamp-columns-in-mysql-4-0 mysql and php in general: https://www.w3schools.com/php/php_mysql_intro.asp If my answer works it would be nice if you mark it as the solution because it took me a lot of time. And StackOverflow is actually not there to solve the tasks but only to give solutions to problems. – noah1400 Nov 25 '21 at 14:24
  • Appreciate the effort and time I will take it from here I think I already got all the necessary information/tools to achieve my desired result. – C4DXYZ-SO Nov 25 '21 at 16:22