Dynamic elements with socket (Template7)

Hi,
currently I’m working with Framework7 on a front-end for my smart home system. All states, e.g. lights (on/off/brightness) and doors (opened/closed), are served via a socket.io plugin. In the example file there is this function:

onUpdate: function (id, state) {
  setTimeout(function () {
    console.log('NEW VALUE of ' + id + ': ' + JSON.stringify(state));
    states[id] = state;
  }, 0);
},

Every time a state is updated, it will be changed in the array states[] and looks like this:

> states["myLamp#Switch"]
{val: false, ack: true, …}

My question is now, how I should implement this with Framework7/Template7? Can I pipe this array states[] into Template7, load a template page and have it continuously updated (without reloading the page)?


If I add this line:

document.getElementById('foo').innerHTML = states["myLamp#Switch"]["val"];

to onUpdate() I’m able to see the current value in a <div>, but then I would have to do it for every element in my app… is there a more efficient way to achieve this?

The next step would be to change a state with Framework7, e.g. turning on a light. Currently my idea is to use a click event listener on buttons and then calling the function setState().

Thanks
KLVN

KLVN,

You need to “emit” the event to the app from within the “onUpdate” function. You can pass in the data from socket.io when you emit to the app. And then on the specific component page you are trying to refresh, you would catch that emit and then use this.$setState command to refresh the page dynamically.

This is a link to an earlier thread when I asked a similar question and @nolimits4web responded, helping me out.

  • Matt
3 Likes

Hi Matt,
thank you for answering. “emit” was the key, so that I’m now able to see the current state with a toggle.

This is my current code:

var app = new Framework7();

// this is just for testing
var toggle = app.toggle.get('.lamp-kitchen');

// if states are updated, set toggle to specific state
app.on('smarthomeStates', function (states) {
  toggle.checked = states["lamp-kitchen#Switch"]["val"];
});

// this is the socket.io adapter for ioBroker (smart home system)
var states = [];
servConn.init({
  [...]
  onUpdate: function (id, state) {
      setTimeout(function () {
        states[id] = state;
        app.emit('smarthomeStates', states);
      }, 0);
    },
  [...]
});

This was just to test things out. I have a list with many toggles and different classes for each one of them (.lamp-kitchen, .lamp-bathroom, …) and how can I update all of them without doing it like in my code above? On one hand I need to listen to changes in states and apply them to the corresponding HTML-classes and on the other hand I also have to listen to changes in the elements on the page.
Is there something like:

app.on('smarthomeStates', function (states) {
  // iterate through all elements and set the new value
});

allElements.on('elementChanged', function (id, newState) {
  // socket.io: setState(id, newState);
}); 

?

I’d like to give every element (toggle, button, slider) a data attribute to identify them. In JS I’d have to load the states, look for all elements with an attribute and then update it. Vice versa if an element with data-id is changed, it should send the new value with this id.

<label class="toggle toggle-init lamp-kitchen" data-id="philips.hue.1234#Switch">
  <input type="checkbox"/>
  <span class="toggle-icon"></span>
</label>

(For now I scratched the idea with Template7, because otherwise I would have to make lists will all the lights/classes.)

Thanks

What you ask seems to be close to “two ways data-binding”.
VueJS (or React but I do not know it well) makes it really easy, and is completely integrated with Framework7.

Thanks, I’ll take a look at it.

Or check how it can be achieved using Router components in Core Framework7 https://framework7.io/docs/router-component.html

Hi Vladimir, I’m not quite sure what part of the documentation is relevant for me. From what I understand, I have to use a Virtual DOM (https://framework7.io/docs/router-component.html#virtual-dom) to “re-build” the template with current states. But I couldn’t find the part that achieves this “two ways data-binding”.

It depends on what you call the “two ways data-binding”.
Call $setState -> to update the View (DOM)
Handle some component’s (page’s) input/toggle/checkbox -> to update the state with ($setState in event handlers) -> which will update the DOM (2-ways data binding)

1 Like

So this is what I’m currently working on and it kinda works.
If the array states is changed, the first method will go through all HTML elements that I assigned the class iobroker-state and depending on its type (defined by its other classes) the elements are set to the values in states.
Second method works the other way around: If an HTML element is changed, it will look for its type and send the new value via the socket.

But the problem now is that socket.io is constantly sending states and if it’s exactly that moment, when a value is received and a new one is send, it will create an infinite loop and for example the range-slider will flicker.

Can I somehow pause app.on('smarthomeStates', function (states) while I send new values to the server?

It’s like server and client are fighting over which value to set the HTML elements to.

// RECEIVE states and update
app.on('smarthomeStates', function (states) {
  elements = document.getElementsByClassName("iobroker-state")

  for(var i = 0; i < elements.length; i++) {
    if ((elements[i].classList).contains("toggle")) {
      app.toggle.get(elements[i]).checked = states[elements[i].dataset.id]["val"];

    } else if ((elements[i].classList).contains("range-slider")) {
      app.range.get(elements[i]).setValue(states[elements[i].dataset.id]["val"]);
    }
  }
});

// TRANSMIT states on change
$('.iobroker-state').on('touchend mouseleave', function (e) {
  if ((e.srcElement.classList).contains("toggle")) {
    var toggle = app.toggle.get(e.target);
    console.log(toggle);
    servConn.setState(e.srcElement.dataset.id, toggle.checked)
  
  } else if ((e.srcElement.classList).contains("range-slider")) {
    var range = app.range.get(e.target);
    servConn.setState(e.srcElement.dataset.id, range.value)
  }

});