I am trying to make an paper-card element change colors based on the status of the customers data on Fire base, but for some reason the color only updates on the second click of the customer. Right now I have the paper cards ID set to the firebase data in order to make it change colors. Here's my elements style code:
<style is="custom-style">
:host {
display: block;
}
#cards {
#apply(--layout-vertical);
#apply(--center-justified);
}
.row {
padding: 20px;
margin-left: 10px;
}
paper-card {
padding: 20px;
}
#check {
float: right;
bottom: 15px;
--paper-card
}
#Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
#Default {
/*Apply Default Style*/
/*--paper-card-content: {*/
/* background: var(--paper-red-500);*/
/*};*/
}
paper-icon-button.check{
color: var(--paper-green-500);
}
paper-icon-button.check:hover{
background: var(--paper-green-50);
border-radius: 50%;
}
#check::shadow #ripple {
color: green;
opacity: 100%;
}
.iron-selected{
color: green;
}
And here is the template:
<template>
<firebase-collection
location="https://calllistmanager.firebaseio.com/Wilson"
data="{{wilsonData}}"></firebase-collection>
<div id="cards">
<template id="cards" is="dom-repeat" items="{{wilsonData}}" as="customer">
<paper-card id="{{customer.status}}" class="{{customer.status}}" heading="[[customer.__firebaseKey__]]">
<div class="card-content">
<span>Phone: </span><span>[[customer.number]]</span>
<span>Status: </span><span>[[customer.status]]</span>
<paper-icon-button style="color: green" id="check" on-tap="checktap" icon="check">
</paper-icon-button>
</div>
</paper-card>
</template>
</div>
Here is my script:
<script>
(function() {
Polymer({
is: 'list-display',
properties: {
wilsonData: {
type: Object,
observer: '_dataObserver'
}
},
ready: function() {
var listRef = new Firebase("https://calllistmanager.firebaseio.com/Wilson");
},
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var stat;
var store = ref.child(e.model.customer.__firebaseKey__);
store.on("value", function(snapshot){
stat = snapshot.child("status").val();
});
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.customer.status = "Default";
}
else {
store.update({
"status": "Done"
});
e.model.customer.status = "Done";
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles()
}
});
})();
at first I thought the problem may be that the function runs updateStyles(); faster than firebase can update but it always works fine on the second click...any suggestions?
I think the problem could be caused by the call to firebase. store.on("value", is not a synchronous function. However, later in your code you assume that you already have a value, that will be set later on whenever the value event fires. You could try adding the rest of your code in the event handler. Like this:
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var store = ref.child(e.model.customer.__firebaseKey__);
store.once("value", function(snapshot){
var stat = snapshot.child("status").val();
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.set("customer.status", "Default");
}
else {
store.update({
"status": "Done"
});
e.model.set("customer.status", "Done");
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles();
}.bind(this));
}
Essentially, you wait until the stat variable has been set to do the rest of your tasks. Also note, the bind(this) at the end, which will allow you to update the the styles from the event handler.
Update
There are a couple of more issues. First it's better to uses classes for changing the styles and not IDs. IDs should not change. Then, to bind to the class attribute, use the $ sign. When you update the model, you should use the set API.
Have a look at this plunker. It is a small working example (only works in Chrome) that changes styles when you click the checkmark. It does not use Firebase, however.
Here's how you could to the style with classes.
.Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
And in your template:
<paper-card class$="{{customer.status}}" heading="[[customer.__firebaseKey__]]">
Related
I have a International country code selector code for my website but the problem I'm facing is that I want to hide the dropdown menu when anyone clicks outside the container, I have already written the code for this feature but the bug I'm facing right now is that It closes the dropdown menu when anyone clicks inside of the container, not the outside. it's doing the exact opposite.
I want to hide the menu when clicked outside.
Please check out my Highlighted code -
// Cache the elements
const xbutton = document.querySelector('.country-code-selector .telcode');
const container = document.querySelector('.country-code-selector .container');
const input = document.querySelector('.country-code-selector input');
const list = document.querySelector('.country-code-selector .list');
// Add event listeners to the button, input, and list
// We use a process called "event delegation" on the list
// to catch events from its children as they "bubble up" the DOM
// https://dmitripavlutin.com/javascript-event-delegation/
xbutton.addEventListener('click', handleButton);
input.addEventListener('input', handleInput);
list.addEventListener('click', handleListClick);
document.addEventListener('click', handleDocumentClick);
// Handles the document click - it checks to see if the clicked
// part of the document has a parent element which is either
// `null` or is the HTML element, and then closes the container
// if it's open
function handleDocumentClick(e) {
const { parentElement } = e.target;
}
/************************************************************************/
/****************** Hide the menu when clicked outside ******************/
/************************************************************************/
document.addEventListener("click", (event) => {
if (container.contains(event.target)) {
if (container.classList.contains('show')) {
container.classList.remove('show');
}
}
});
/************************************************************************/
/****************** Hide the menu when clicked outside ******************/
/************************************************************************/
// All of the data held as objects within an array
const data = [
{ name: 'Afganistan', code: '69', flag: 'afg' },
{ name: 'Barbados', code: '1-246', flag: 'brb' },
{ name: 'Bolivia', code: '591', flag: 'bol' },
{ name: 'Cuba', code: '53', flag: 'cub' },
{ name: 'Fiji', code: '679', flag: 'fji' },
];
// Filters the data based on the characters
// at the start of the provided name
function filterData(data, value) {
return data.filter(obj => {
return (
obj.name.toLowerCase().startsWith(value.toLowerCase())
|| obj.code.toLowerCase().startsWith(value.toLowerCase())
);
});
}
// Create a series of list items based on the
// data passed to it
function createListHtml(data) {
return data.map(obj => {
const { name, code, flag } = obj;
return `
<li
class="item"
data-name="${name}"
data-code="${code}"
data-flag="${flag}"
>
<div class="flag-icon flag-icon-${flag}"></div>
<div class="name">${name} (+${code})</div>
</li>
`;
}).join('');
}
// Toggle the container on/off
function handleButton() {
container.classList.toggle('show');
}
// No data available list item
function createNoDataHtml() {
return '<li class="nodata">No data available</li>';
}
// When the input is changed filter the data
// according to the current value, and then
// create some list items using that filtered data
function handleInput(e) {
const { value } = e.target;
if (value) {
const filtered = filterData(data, value);
if (filtered.length) {
list.innerHTML = createListHtml(filtered);
} else {
list.innerHTML = createNoDataHtml();
}
} else {
list.innerHTML = createListHtml(data);
}
}
// Create some button HTML
function createButtonHtml(code, flag) {
return `
<div class="flag-icon flag-icon-${flag}"></div>
<div class="code">+${code}</div>
`;
}
// Updates the selected list by removing the `selected`
// class from all items, and then adding one to the clicked
// item
function updateSelected(list, item) {
const items = list.querySelectorAll('.item');
items.forEach(item => item.classList.remove('selected'));
item.classList.add('selected');
}
// When an item is clicked, grab the relevant data
// attributes, create the new button HTML, and then
// close the container
function handleListClick(e) {
const item = e.target.closest('li') || e.target;
if (item.classList.contains('item')) {
const { code, flag } = item.dataset;
xbutton.innerHTML = createButtonHtml(code, flag);
updateSelected(list, item);
container.classList.remove('show');
}
}
list.innerHTML = createListHtml(data);
.country-code-selector .telcode {
margin-bottom: 1em;
display: flex;
}
.country-code-selector .telcode div.code, .item div.name {
margin-left: 0.25em;
}
.country-code-selector .container {
display: none;
width: 350px;
border: 1px solid black;
}
.country-code-selector .show {
display: block;
}
.country-code-selector .list {
height: 100px;
list-style: none;
margin: 1em 0 0 0;
padding: 0;
overflow-y: scroll;
border: 1px soldi darkgray;
}
.country-code-selector .item {
display: flex;
padding: 0.25em;
border: 1px solid lightgray;
}
.country-code-selector .item:hover {
cursor: pointer;
}
.country-code-selector .item:hover, .item.selected {
background-color: lightyellow;
}
<link href="https://amitdutta.co.in/flag/css/flag-icon.css" rel="stylesheet"/>
<div class="country-code-selector">
<button type="button" class="telcode">Tel code</button>
<section class="container">
<input type="text" placeholder="Search for country" />
<ul class="list"></ul>
</section>
</div>
I’m trying to figure out how to keep “Mark as Read” as “Mark as Unread” even after refreshing the page. Vice versa too. How do I save the data with localStorage? So far, this is my code for “Mark as Read”:
function readunread() {
currentvalue = document.getElementById("readunread").value;
if(currentvalue == "Mark as Unread"){
document.getElementById("readunread").value = "Mark as Read";
} else{
document.getElementById("readunread").value = "Mark as Unread";
}
}
body {
background:black;
}
.button {
border: none;
color: white;
font-family: Corbel;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
background-color: black;
}
input[type=button] {
font-size: 20px;
font-family: Corbel;
text-decoration: none;
color: white;
border: none;
background: none;
cursor: pointer;
margin: 0;
padding: 0;
}
<input type = "button" value = "Mark as Read" id = "readunread" onclick = "readunread();">
I click the “Mark as Read” and it becomes “Mark as Unread.” But after refreshing the page, it goes back to “Mark as Read.” How do I avoid that?
In your scripts you'll need to change two things:
<script>
function readunread() {
currentvalue = document.getElementById("readunread").value;
if (currentvalue == "Mark as Unread") {
document.getElementById("readunread").value = "Mark as Read";
// 1. Update the localstorage
localStorage.setItem("readunread", "Mark as Read");
} else {
document.getElementById("readunread").value = "Mark as Unread";
// 1. Update the localstorage
localStorage.setItem("readunread", "Mark as Unread");
}
}
</script>
<input
type="button"
value="Mark as Read"
id="readunread"
onclick="readunread();"
/>
<script>
// 2. Get the value from the local storage
function loadInitialValue() {
const localValue = localStorage.getItem("readunread");
console.log(localValue);
if (localValue == "Mark as Unread") {
document.getElementById("readunread").value = "Mark as Unread";
} else {
document.getElementById("readunread").value = "Mark as Read";
}
}
loadInitialValue(); // Make sure to call the function
</script>
In order to manage the read/unread state of items using localStorage as your persistent data store, you'll need to serialize your (un)read state as some kind of string to store as a value in the storage area (because localStorage only stores string values), and then deserialize the value when you retrieve it. JSON is an accessible choice for the serialization format because it naturally represents many JavaScript data structures and is easy to parse/stringify.
Questions like these are always hard to demonstrate in a working code snippet because Stack Overflow's code snippet environment is sandboxed and prevents access to things like localStorage, so when you try to use those features, a runtime exception is thrown as a result of the lack of permissions. Nonetheless...
Below I've provided a self-contained example of storing the read/unread state for a list of items using basic functional programming techniques to keep the code organized. This is just plain HTML + CSS + JavaScript and doesn't use any frameworks like React, etc. You can copy + paste the code into a local HTML file on your computer and then serve it using a local static file web server (e.g. with Deno or Python, etc.) to see it working. I've included verbose comments for you to explain what's happening at every step of the program.
If you want to examine the state of your localStorage as you test the demo, see the question How to view or edit localStorage?.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LocalStorage: read/unread items</title>
<style>
/* Just some styles for this example: styling is up to you */
* { box-sizing: border-box; }
body { font-family: sans-serif; }
.toggle-status {
font-size: 1rem;
padding: 0.25rem;
width: 8rem;
}
#list {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: flex-start;
}
.item {
display: flex;
gap: 1rem;
align-items: center;
}
.item.read > .item-content { font-weight: normal; }
.item.unread > .item-content { font-weight: bold; }
</style>
<script type="module">
// Get the state of all of the read/unread items from localStorage
// as an object:
function getStatusMap () {
try {
// Get the JSON value from local storage:
// if it doesn't exist, it will be null, so use a default value instead:
// a JSON string representing an empty object:
const storageValue = window.localStorage.getItem('read_status_map') ?? '{}';
// Parse the string value into an actual object:
const readStatusMap = JSON.parse(storageValue);
// Return the value if it's a plain object:
if (
typeof readStatusMap === 'object'
&& readStatusMap !== null
&& !Array.isArray(readStatusMap)
) return readStatusMap;
// Else throw an error because it was an invalid value:
throw new Error('Unepxected value');
}
catch (ex) {
// Catch any exception which might have occurred.
// You can handle it however you want (or just ignore it).
// For example, you could print it
// to the console error stream to view it:
console.error(ex);
// Return an empty object as the default:
return {};
}
}
// Update the localStorage state of all the read/unread items:
function setStatusMap (statusMap) {
const json = JSON.stringify(statusMap);
const storageValue = window.localStorage.setItem('read_status_map', json);
}
// Update the read/unread status for a single item:
function updateStatus (statusMap, listItemElement, isReadStatus) {
const button = listItemElement.querySelector(':scope > button.toggle-status');
// Depending on the current status, update the action button's text
// to describe the next (opposite) action:
button.textContent = `Mark as ${isReadStatus ? 'unread' : 'read'}`;
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Get the state object of the current item from the status map object,
// OR create one if it doesn't exist yet. You can store other information
// about each item here, but — in this example — only the ID (string)
// and read status (boolean) properties are stored:
const status = statusMap[id] ??= {id, isRead: false};
// Update the read status of the item:
status.isRead = isReadStatus;
// Update the whole state in localStorage:
setStatusMap(statusMap);
// Optional: update the list item's read/unread class.
// This can help with applying CSS styles to the items:
if (isReadStatus) {
listItemElement.classList.add('read');
listItemElement.classList.remove('unread');
}
else {
listItemElement.classList.remove('read');
listItemElement.classList.add('unread');
}
}
// A convenience function which toggles between read/unread for an item:
function toggleStatus (statusMap, listItemElement) {
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Get the current status (or false by default if it doesn't exist yet):
let isRead = statusMap[id]?.isRead ?? false;
// Toggle it to the opposite state:
isRead = !isRead;
// Update it:
updateStatus(statusMap, listItemElement, isRead);
}
// Now, using the functions above together:
function main () {
// Get the initial read/unread status map:
const statusMap = getStatusMap();
// Get an array of the item elements:
const listItemElements = [...document.querySelectorAll('#list > li.item')];
for (const listItemElement of listItemElements) {
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Set the initial read status for each item to what was found
// in localStorage, or if nothing was found then set to false by default:
const initialStatus = statusMap[id]?.isRead ?? false;
updateStatus(statusMap, listItemElement, initialStatus);
const button = listItemElement.querySelector(':scope > button.toggle-status');
// Set an action for each item's toggle button: when it is clicked,
// toggle the status for that item. Formally, this is called "binding an
// event listener callback to the button's click event":
button.addEventListener(
'click',
() => toggleStatus(statusMap, listItemElement),
);
}
}
// Invoke the main function:
main()
</script>
</head>
<body>
<!--
A list of items, each with:
- a unique ID
- a toggle button,
- and some text content
-->
<ul id="list">
<li class="item" data-id="cc9e88ce-3ed4-443a-84fc-fa7147baa025">
<button class="toggle-status">Mark as read</button>
<div class="item-content">First item content</div>
</li>
<li class="item" data-id="23a9204c-905f-48db-9f6a-deb3c8f82916">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Second item content</div>
</li>
<li class="item" data-id="18b47e4c-635f-49c0-924e-b9088538d08a">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Third item content</div>
</li>
<li class="item" data-id="ed2aacca-64f0-409d-8c1b-d1bdcb7c6058">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Fourth item content</div>
</li>
<li class="item" data-id="0fce307b-656a-4102-9dc9-5e5be17b068d">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Fifth item content</div>
</li>
<!-- ...etc. -->
</ul>
</body>
</html>
You were really close. What you have left is to use the local storage. For that, replace your JavaScript by the code below:
// On Load
const readUnreadButton = document.getElementById("readunread");
document.getElementById("readunread").value =
localStorage.getItem("readunread") || readUnreadButton.value;
// On Click
function readunread() {
const readUnreadButton = document.getElementById("readunread");
currentvalue = readUnreadButton.value;
if (currentvalue == "Mark as Unread") {
readUnreadButton.value = "Mark as Read";
localStorage.setItem("readunread", "Mark as Read");
} else {
readUnreadButton.value = "Mark as Unread";
localStorage.setItem("readunread", "Mark as Unread");
}
}
Given some text:
<div>text</div>
I would like to detect when the computed CSS property color changes for this div.
There could be a number of css queries that would change its color, like media queries:
div {
color: black;
#media (prefers-color-scheme: dark) {
color: white;
}
#media screen and (max-width: 992px) {
background-color: blue;
}
}
Or perhaps a class applied to a parent:
div {
}
body.black {
color: white;
}
How can I, using Javascript, observe this change of computed style?
I think we can get part way there at least by using mutation observer on the whole html, that will detect things like change to the attributes of anything, which may or may not influence the color on our to be 'observed' element, and by listening for a resize event which will at least catch common media query changes. This snippet just alerts the color but of course in practice you'll want to remember the previous color (or whatever you are interested in) and check to see if it is different rather than alerting it.
const observed = document.querySelector('.observed');
const html = document.querySelector("html");
let style = window.getComputedStyle(observed);
// NOTE: from MDN: The returned style is a live CSSStyleDeclaration object, which updates automatically when the element's styles are changed.
const observer = new MutationObserver(function(mutations) {
alert('a mutation observed');
mutations.forEach(function(mutation) {
alert(style.color);
});
});
function look() {
alert(style.color);
}
observer.observe(html, {
attributes: true,
subtree: true,
childList: true
});
window.onresize = look;
.observed {
width: 50vmin;
height: 50vmin;
color: red;
}
#media (min-width: 1024px) {
.observed {
color: blue;
}
}
#media (max-width: 700px) {
.observed {
color: gold;
}
<div class="observed">See this color changing.<br> Either click a button or resize the viewport.</div>
<button onclick="observed.style.color = 'purple';">Click for purple</button>
<button onclick="observed.style.color = 'magenta';">Click for magenta</button>
<button onclick="observed.style.color = 'cyan';">Click for cyan</button>
What I don't know is how many other things might influence the setting - I see no way of finding out when the thing is print rather than screen for example. Hopefully someone will be able to fill in any gaps.
I wrote a small program that detects change in a certain CSS property, in getComputedStyle.
Note: Using too many observeChange() may cause performance issues.
let div = document.querySelector("div")
function observeChange(elem, prop, callback) {
var styles = convertObject(getComputedStyle(elem));
setInterval(() => {
let newStyle = convertObject(getComputedStyle(elem));
if (styles[prop] !== newStyle[prop]) { //Check if styles are different or not
callback(prop, styles[prop], newStyle[prop]);
styles = newStyle; //Set new styles to previous object
}
},
500); //You can change the delay
}
//Callback function
function callback(prop, old, newVal) {
console.log(prop + " changed from " + old + " to " + newVal);
}
observeChange(div, "color", callback)
//Convert CSS2Properties object to a normal object
function convertObject(obj) {
let object = {}
for (let i of obj) {
object[i] = obj[i]
}
return object
}
div {
color: green;
}
input:checked+div {
color: red;
}
<input type="checkbox">
<div>Hello World</div>
Im new to Blazor. Have a js function that uses gojs library. Canvas, data loading are processed dynamically on js side. As I undestood, just adding js script on Host is not enouph and I have to use IJSRuntime. From examples I found it is understandable when calling simple functions with return etc. But I have this:
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
initialContentAlignment: go.Spot.Center,
allowDrop: true, // must be true to accept drops from the Palette
"LinkDrawn": showLinkLabel, // this DiagramEvent listener is defined below
"LinkRelinked": showLinkLabel,
"animationManager.duration": 800, // slightly longer than default (600ms) animation
"undoManager.isEnabled": true // enable undo & redo
});
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function (e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
myPalette =
$(go.Palette, "myPaletteDiv", // must name or refer to the DIV HTML element
{
"animationManager.duration": 800, // slightly longer than default (600ms) animation
nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
model: new go.GraphLinksModel([ // specify the contents of the Palette
{ category: "Start", text: "Start", figure: "Ellipse" },
{ text: "Step" },
{ text: "Check Trigger", figure: "Diamond" },
{ category: "End", text: "End" }
//{ category: "Comment", text: "Comment" }
])
});
}
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById("jsonModel").value);
}
and so on. So, on init my canvas are dynamically built.
on component side I have :
<div onload="init()"></div>
<span style="display: inline-block; vertical-align: top; padding: 5px; width:20%">
<div id="myPaletteDiv" style="border: solid 1px gray; height: 100%">
``` </div>```
``` </span>```
``` <span style="display: inline-block; vertical-align: top; padding: 5px; width:80%">```
``` <div id="myDiagramDiv" style="border: solid 1px gray; height: 100%"></div>```
```</span>```
<textarea style="display:inline-block" id="jsonModel"></textarea>
How can I process all this in Blazor? Tried
[JSInvokable]
protected override Task OnAfterRenderAsync(bool firstRender = true)
{
if (firstRender)
{
return jsRuntime.InvokeVoidAsync
("init").AsTask();
}
return Task.CompletedTask;
}
but it didn`t work.
Thank you in advance for any help
There is a complete sample at: https://github.com/NorthwoodsSoftware/GoJS-Alongside-Blazor
It does this:
protected async override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// This calls the script in gojs-scripts.js
await JSRuntime.InvokeAsync<string>("initGoJS");
}
}
Alas, I am not familiar with Blazor, so I'm not able to answer any questions about it.
I'm trying to synchronize a css transition in a directive with web socket events in it's parent controller.
For example, here is a scenario: User clicks on a product that he wants to purchase, I send a message to websocket server - upon receiving a successful response, I need to animate the product moving to users purchase cart and when this animation is complete, I have to actually add the product to users cart (I don't have access to this cart inside the directive)
Below is the code I have so far. Consider the pink box as a purchasable product, and the blue boxes inside bordered box as purchased products. User can purchase new product by clicking the pink box.
(For demo purpose, I'm manually calling $scope.purchaseSuccess, in real scenario it'll be invoked by socket.io)
angular.module('test', [])
.controller('testCtrl', function($scope) {
$scope.products = [{}, {}, {}, {}];
$scope.purchased = [{}];
$scope.purchase = function() {
//emits socket io message here
$scope.purchaseSuccess($scope.products.pop());
//--^--- this is called manually for demo purpose
};
$scope.purchaseSuccess = function(product) {
//success event handler called by socket io
$scope.$broadcast('purchase-success', product);
};
$scope.$on('transition-end', function(product) {
$scope.purchased.push(product);
$scope.$apply();
});
})
.directive('testDir', function() {
return {
scope: {},
link: function(scope, element) {
$helper = element.find('.helper');
var temp; // used to cache the product received from controllers event
scope.$on('purchase-success', function(product) {
temp = product; // cache the product to be sent back after animation
$helper.removeClass('no-transition');
$target = $('.purchased .product:last');
$helper.position({
my: 'top left',
at: 'top right',
of: $target,
using: function(pos, data) {
var val = 'translate3d(' + (pos.left + data.element.width) + 'px,' + pos.top + 'px,0px)'
$(this).css('transform', val);
}
});
});
$helper.on('transitionend MSTransitionEnd webkitTransitionEnd oTransitionEnd', function() {
$helper.addClass('no-transition');
$helper.css('transform', 'translate3d(0%,0px,0px)');
scope.$emit('transition-end', temp);
});
}
};
});
.product {
height: 50px;
width: 50px;
border: 1px solid;
}
.stock {
position: relative;
height: 50px;
background: silver;
}
.stock .product {
position: absolute;
background: hotpink;
}
.stock .product.helper {
transition: transform 2s;
}
.purchased {
height: 60px;
margin-top: 50px;
border: 2px dotted;
}
.purchased .product {
display: inline-block;
margin: 5px;
background: dodgerblue;
}
.no-transition {
transition: none !important;
}
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
<script src="https://code.angularjs.org/1.4.8/angular.js"></script>
<div ng-app="test" ng-controller="testCtrl">
<div class="stock" data-test-dir data-purchase="purchase">
<div class="product helper no-transition">
</div>
<div class="product" ng-click="purchase()">
</div>
</div>
<div class="purchased">
<div class="product" ng-repeat="product in purchased">
</div>
</div>
</div>
This is the tiniest demo I was able to make, the actual thing has further complications like timers running within which user has to finish all purchases
So, basically:
User's click invokes scope method which emits event to socket server
Socket.io success event handler broadcasts an angular event
Directive receives this event, caches the received data in a
temporary variable and triggers the CSS transition
When the animation is complete, directive emits an event with the
cached data
Controller receives this event and acts upon the data.
But this doesn't seem right. Is there a way to avoid sending this data back and forth..? Maybe a better way to achieve this without all the event emissions..?
Side note: those who aren't familiar with socket io, please consider purchase as a method that sends an ajax request, purchaseSuccess as it's success handler which should trigger the animation, and upon completion of this animation I need to do some action in the controller scope.