HOAB

History of a bug

Angular @Input array was not updated.

Rédigé par gorki Aucun commentaire

Problem :

Here again, a common problem, the @Input attribute of the child element was not updated.

Good to know : 

  • ngOnChanges is called when the @Input attribute is already updated
  • common problem : the @Input parameter is an array and its reference is not modified, i.e, the array is the same even if it has different elements or new elements

No, in my case, the symptoms was : 

  1. ngOnChanges input is launch
  2. SimpleChanges event contains the data in “currentValue
  3. but array is displayed as [] even with values.

Yeah, tricky. I understand that array may be an object instead of a real array… but why. 

Solution :

Part of the problem was that the value sent to @Input was an array, but not initialized.

And a small function used to have unique value : 

  private pushIfNotExist<T>(array:T[], element:T) {
    if (array.indexOf(element) === -1) {
      array.push(element);
    }
  };

and I call it like that : 

pushIfNotExist(myArray, randomValue)

And with an empty javascript variable, well : no problem, it works, in a given way.

Adding : 

myArray = []

just before, everything works as a charm.

I lose time here, because I had a doubt on where was the issue : display on browser console, workflow for triggering ngOnChange, …

 

 

Lire la suite de Angular @Input array was not updated.

Facebook and Woocommerce - resync

Rédigé par gorki Aucun commentaire

Problem :

Times to times, Facebook and Woocommerce lost their synchronization. 

In this case, the solution is to reset everything (if you have on Facebook, only your Woocommerce products !!)

Some information there :

But if your catalog is connected to a shop you can not delete your catalog…

And for any reason, the bulk requests was not working for me : accepted, no error, but still all the products ! 

I used : 

https://graph.facebook.com/v15.0/<my shop>/items_batch

{"allow_upsert": "true", "item_type": "PRODUCT_ITEM", "requests": [
{"method": "DELETE", "data": {"id": "1234"}},
{"method": "DELETE", "data": {"id": "2345"}}
{"method": "DELETE", "data": {"id": "3456"}}
]}

Solution :

So I tried to build the request by myself with a short python file : 


import requests

BASE_URL_FACEBOOK = 'https://graph.facebook.com/v16.0'
BASE_URL_FACEBOOK_BOUTIQUE = f'{BASE_URL_FACEBOOK}/<your shop>'
TOKEN = "yourToken"

# You must initialize logging, otherwise you'll not see debug output.
#logging.basicConfig()
#logging.getLogger().setLevel(logging.DEBUG)

url="/products?bulk_pagination=false&summary=true&limit=1000"
print(f'Calling GET : {url}')
hdr = {'User-Agent': r'Facebook-for-WooCommerce/2.6.27 (WooCommerce/6.8.2; WordPress/5.9.5)', 'Accept':'*/*', 'Content-Type': 'application/json', 'Authorization': f"Bearer {TOKEN}"}
response = requests.get(BASE_URL_FACEBOOK_BOUTIQUE + url, headers=hdr)
if response.status_code == 200 or response.status_code == 201:
    print(f'GET OK')
    products_response = response.json()
    delete_products_requests = list()
    print(products_response['summary']['total_count'])
    for product in products_response['data']:
        id = product['id']
        delete_command = {'method': 'DELETE', 'data': {"id":id}}
        delete_products_requests.append(delete_command)
        
    delete_request = dict()
    delete_request['allow_upsert'] = 'true'
    delete_request['item_type'] = 'PRODUCT_ITEM'
    delete_request['requests'] = delete_products_requests

    url="/items_batch"
    print(json.dumps(delete_request))
    response = requests.post(BASE_URL + url, headers=hdr, data=json.dumps(delete_request))
    print(response.status_code)
    print(response.text)
    response.raise_for_status()
    handles = response.json()['handles']
    print(f'handle = {handles}')

And follow the request with : 

https://graph.facebook.com/v16.0/<shop id>/check_batch_request_status?handle=<myHandle from previous request>

Still no error, but nothing deleted (we can follow the batch request in 

Facebook Business Manager > Catalog > Data Source > Application 

At the end, I found the delete request on a single product (simple by slowest) :

import requests


BASE_URL_FACEBOOK = 'https://graph.facebook.com/v16.0'
BASE_URL_FACEBOOK_BOUTIQUE = f'{BASE_URL_FACEBOOK}/<your shop>'
TOKEN = "yourToken"

url="/products?bulk_pagination=false&summary=true&limit=1000"
print(f'Calling GET : {url}')
hdr = {'User-Agent': r'Facebook-for-WooCommerce/2.6.27 (WooCommerce/6.8.2; WordPress/5.9.5)', 'Accept':'*/*', 'Content-Type': 'application/json', 'Authorization': f"Bearer {TOKEN}"}
response = requests.get(BASE_URL_FACEBOOK_BOUTIQUE + url, headers=hdr)
if response.status_code == 200 or response.status_code == 201:
    print(f'GET OK')
    products_response = response.json()
    products = dict()
    delete_products_requests = list()
    print(products_response['summary']['total_count'])
    for product in products_response['data']:
        id = product['id']
        url=f"/{id}"
        response = requests.delete(BASE_URL_FACEBOOK + url, headers=hdr)
        print(f'{response.status_code} : {response.text}')
        if (response.status_code != 200):
            print(f'Can not delete product {id}')

 

 

 

 

Wordpress API refuse python rest request

Rédigé par gorki Aucun commentaire

Problem :

Simple test : 

response=requests.get('https://wordpress.site/wp-json/wc/v3/products/attributes',headers=hdr,    
						auth=requests.auth.HTTPBasicAuth('login', 'password'))

But for any reason I received a 403 command.

Same command with curl works : 

curl -vvvv -u login:password https://wordpress.site/wp-json/wc/v3/products/attributes

Well… 

Solution :

Simple, but don't know why.

Default user agent for Python : 

User-Agent: python-requests/2.28.1

Default user agent for curl

user-agent: curl/7.86.0

Well it works with (add a space before the /) : 

User-Agent: python-requests /2.28.1

Still a mystery. Not from python side, maybe from planethoster.com side or wordpress ? One day, I will have time to go further… 

 


 

 

 

Lire la suite de Wordpress API refuse python rest request

React and events

Rédigé par gorki Aucun commentaire

Problème :

J'utilise React pour développer une application frontend et j'utilise les méthodes de base Javascsript pour gérer les événements venant du serveur ou en interne Front.

J'ai trouvé sur internet quelques fonctions utiles pour gérer les événements (je n'ai pas retrouvé où !) :

  • on : abonnement
  • off : désabonnement
  • trigger : déclencher un évenement

Pour mon usage, j'ai customisé ces fonctions en ajoutant une callback en paramètre. Résultat j'ai ce code : 

function on(eventType:string, state:any, listener:(detail:MyEvent) => void) {
    const resultFunction = function(event:any) {
        listener(event.detail)
    };
    document.addEventListener(eventType, resultFunction);

Cela me permet de gérer des événements qui héritent de MyEvent.

Le problème est que, ce faisant, je crée une fonction anonyme à chaque souscription et que du coup il est impossible d'utiliser removeEventListener lorsque le composant se démonte.

Solution :

Indirectement c'est ce guide qui m'a aidé : https://dev.to/marcostreng/how-to-really-remove-eventlisteners-in-react-3och

L'idée était donc bien de garder la référence à la fonction anonyme créée (qui me permet de gérer des callbacks génériques), mais comment simplifier l'usage pour éviter de devoir faire stocker la référence à cette fonction anonyme par tous les composants ?

Eh bien en la stockant moi-même directement dans le state !

Sur le désabonnement, je vais chercher dans le state, la référence à la fonction anonyme stockée pour mon type d'événement.


function buildKey(eventType: string) {
    return 'clientside-events.listener.' + eventType;
}

function on(eventType:string, state:any, listener:(detail:MyEvent) => void) {
    const resultFunction = function(event:any) {
        listener(event.detail)
    };
    document.addEventListener(eventType, resultFunction);
    if (state !== null) {
        if (!state.listeners) {
            state.listeners = new Map();
        }
        state.listeners.set(buildKey(eventType), resultFunction);
    }
    return resultFunction;
}

function off(eventType:string, state:any) {
    if (state !== null && state.listeners) {
        document.removeEventListener(eventType, state.listeners.get(buildKey(eventType)));
    } else {
        console.warn('Trying to remove a listener but not found for ' + eventType)
    }
}

function trigger(eventType:string, message:ShaanEvent) {
    const event = new CustomEvent(eventType, { detail: message });
    document.dispatchEvent(event);
}

export { on, off, trigger };

 

How-to extend Angular mat-table

Rédigé par gorki Aucun commentaire

Problem :

I would like to have a common table component that makes :

  • table, multicolumn
  • customize row renderer
  • sort
  • pagination
  • export
  • filtering

Well I'm using angular-material, not bad and with mat-table out of the box :

  • sorting : MatSort
  • pagination : MatPaginator
  • export : MatExporter with this extension

But integrate a filter (I try from the same author this one) was not as good as I wanted.

Solution :

After a few hours of googling, I found :

  • An extension : https://github.com/kerrexwong/ExtendedMatTable
    • But there wasn't the possibility to customize the content of the column
    • It was the source of the idea
  • A few stackoverflow talk about the same idea, but nothing working easily in Angular 11 or easy to use
    • https://stackoverflow.com/questions/62961776/is-it-possible-to-create-an-angular-material-mat-table-component-with-extra-dire
    • https://betterprogramming.pub/angular-material-build-your-own-generic-mattable-a49ba68a375a
  • Tried to copy MatTable and extends CDKTable but
    • _headerRowOutlet was always undefined in my copy

Finally from a more standard way :

  1. Create your own component
  2. It takes a few arguments :
    • functionalName : a functional name for the export. I put it in the filename, nothing to do with the display
    • datasource : not a MatTableDataSource, just an array of object
    • headers : the headers, an array of string, one per column
    • Sort must be at this level... Because the ng-container describing the column contains mat-sort-header for sorting... So we'll pass the the sort to the generic table.
<app-hpa-table [functionalName]="'a comprehensive name for export'" [dataSource]="array of object" [headers]="an array of string" [parentSort]="sort" matSort matSortActive="weight">
  1. A list of column definition. "name" is in the array 'headers'. Element comes from the array of objects.
        <ng-container matColumnDef="name">
            <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
            <td mat-cell *matCellDef="let element"><div class="widget-list-name-column">{{element.name}}</div></td>
        </ng-container>
  1. The typescript part of the parent component
    1. ViewChild for sort should be static
@ViewChild(MatSort, {static: true}) sort: MatSort;
  1. The array of objects should be set in one line (not append object by object) to avoid to many update of the generic table

A bit long, I'll describe the generic component another time and try to make an online example.

Classé dans : Web Mots clés : aucun
Fil RSS des articles de cette catégorie