Jump to content
  • 0
andries

Webviewer - FileMaker 19 interactie

Question

Haai haai,

@Peter Wagemans hierbij je voorbeeldje. Het is zonder bibliotheek, maar ik heb wel een eigen klasse moeten schrijven die FileMaker.PerformScript een beetje extend naar FileMaker.PerformScript ( scriptname, parameter, successCallback, errorCallback ).

een klein voorbeeldje om 2 way interactie te gaan doen tussen FileMaker en Webviewer. Dit maakt gebruikt van callbacks en/of Promises, een manier waarmee in Javascript vaak een extern iets wordt aangeroepen en afhankelijk of het allemaal goed is gegaan (success) of er iets is misgegaan (error) laat je dan iets gebeuren in je Javascript applicatie.

Het komt dus eigenlijk hier op neer: "Filemaker, roep dit script aan, als alles goed is gegaan doe dan dit, als alles fout is gegaan doe dan dat." Een concreet voorbeeld voor bijvoorbeeld een kalendarintegratie is. Indien de gebruiker een event van plek veranderd, roep dan FileMaker aan om dit weg te schrijven in de databank. Indien dit NIET gelukt is (error bij wegschrijven naar record bijvoorbeeld), revert dan de beweging en zet het event weer op de oorspronkelijke plaats zodat de gebruiker ziet dat het niet bewaard is. Dit heet asynchroon programmeren, en is eigenlijk een must om goed te snappen als je wil verder gaan met Javascript.

Als een tijdslijn gebeurt dus eigenlijk dit:

1. Actie in Javascript wordt aangeroepen, met daarbij direct de acties: wat als het goed gaat, wat als het mis gaat.
2. Intern worden deze acties geregistreerd, en Javascript roept FileMaker aan.
3. Javascript code stopt nu met werken, dit is ook GEEN PAUZE, we zijn effectief aan het einde van de code gekomen
4. FileMaker doet zijn ding, en bepaalt of hij aan de Webviewer wil zeggen of het goed of slecht is gelopen.
5. De webviewer voert de stukjes code (geregistreerd in stap 1) uit.

In code:

function onEventDrop(event,revertDrop){
  
    //fm.performScript (scriptname, parameter, successCallback, errorCallback )
    fm.performScript("Bewaar Event",event,
       //success callback deze wordt nadien aangeroepen als FileMaker OK terugstuurt
       function ( data ) {
            //data is teruggestuurd vanuit filemaker
            alert("hoi pipeloi, het is bewaard");
       },
       //error callback deze wordt nadien aangeroepen als FileMaker ERROR terugstuurt
       function( error ) {
            //error is teruggestuurd vanuit filemaker
            alert("oeps, foutcode in filemaker: " + code );

            //revertDrop is een functie die we kregen van FullCalendar om een drop te annuleren, dus roepen we die aan.
            revertDrop();
       }
}

 

In FileMaker:

#BUSINESS LOGICA
#hier kan je heel wat logica doen en bijvoorbeeld proberen weg te schrijven naar een filemaker record

#ALS DEMO: een custom dialog
Show Custom Dialog [ Title: "Keuze"; Message: "Is de record bewaard?"; Default Button: “Yes”, Commit: “No”; Button 2: “No”, Commit: “No” ]

If [ Get ( LastMessageChoice ) = 1 ]
    #we pass success to the callback
    Set Variable [ $result; Value:JSONSetElement ( "" ;
[ "id" ; JSONGetElement ( Get ( ScriptParameter ) ; "id" ) ; JSONString ]; [ "success" ; Get ( LastMessageChoice ) = 1 ; JSONString ]
)]
Else If [ Get ( LastMessageChoice ) = 2 ]
    #we pass error to the callback
    Set Variable [ $result; Value:JSONSetElement ( "" ;
[ "id" ; JSONGetElement ( Get ( ScriptParameter ) ; "id" ) ; JSONString ]; [ "error" ; Get ( LastMessageChoice ) = 2 ; JSONString ]
)]
End If

#RETURN TO WEBVIEWER
Perform JavaScript in Web Viewer [ Object Name: "wv"; Function Name: "fmcallback"; Parameter 1: $result ]

 

Zoals je in het voorbeeld zult zien heb ik toch een eigenlijk bibliotheek er in gezet. Heeft me veel bloed zweet en tranen gekost om dit helemaal werkende te krijgen. Ik wil dit gerust wel eens uitleggen hoe het allemaal werkt, maar het komt hier op neer:

Ik heb de FileMaker.performScript een beetje uitgebreid zodat je nu ook een success callback en error callback kunt meegeven. Deze registreer ik, en ik roep dan het filemaker script aan. De parameter pas ik wat aan, want ik voeg er nog een "id" aan toe en dan de parameter zoals doorgegeven.
Bekijk dit als een soort "telefoon lijn waarop filemaker kan terugroepen". Als FileMaker met dit ID de webviewer opnieuw aanroept, weet de webviewer dus om welke actie het ging.

Eigenlijk zegt de webviewer tegen filemaker: hallo, ik ben actie 23 en ik zou willen dat je de record bewaart. Nadien zegt Filemaker tegen de webviewer, actie 23 is helemaal goed gelopen!

En hierbij het voorbeeldje, speel er eens mee, en als er vragen zijn shoot! het is nogal een moeilijk concept, maar éénmaal dat je asynchroon kopieren onder de knie hebt, zou je dit eigenlijk ook in Filemaker willen :)

 

 

Oh ja, voor de diehards: dit werkt ook met Promises (toch op Mac, op Windows niet zeker), dus je kan ook dit doen:
 

fm.performScript ( "getData", "" )
    .then ( (data) => {
       //data is scriptresultaat van van getData
       data.sort();
       fm.performScript ( "storeData", data);
     })
    .then ( ( data ) => {
       //data is script resultaat van storeData
       //doe hier nog dingen
    })
    .catch ( ( error ) => {
        //dit is ofwel de error van getData of van storeData, cool he :)
        alert(error);
 
    })
    

callback.fmp12

Share this post


Link to post

23 answers to this question

Recommended Posts

  • 0

Ik vind het fantastisch! Je hebt hier heel wat energie in gestoken Andries, en je verdient een stukje van mijn chocolade :-) Ik denk dat dit iets is waar je ook mee kan uitpakken binnen een paar dagen op de online dotfmp.

Jammer genoeg moet ik nu aan de slag voor een klant, maar ik ben heel nieuwsgierig, waarschijnlijk ga ik deze middag eens wat proberen.

Share this post


Link to post
  • 0

eigenlijk ging dit vroeger ook al met het hash change trucje dat ik toonde op een fmsummit enkele jaren geleden. Voordeel is dat nu dat hele complexe stuk eruit kan.

Share this post


Link to post
  • 0

Terug javascript aan het leren nadat ik er jaren nauwelijks naar omgekeken heb. Ik kan mezelf dus weer helemaal een noob voelen :-)

Wat voor mij werkt is dat ik de code doorloop en ze probeer aan te passen, zodat ze wat meer doet wat ik wil. Op die manier leer ik hoe de dingen gedaan moeten worden.

Resultaat is dat ik iets compacts heb dat maar met 1 callback functie werkt, maar wél alle parameters doorgeeft van het script resultaat. Leek me wat handiger.

Wat ik nog niet weet is hoe ik de object array terug opkuis nadat een callback uitgevoerd is. @andries weet jij hoe dat moet?

1363115381_Screenshot2020-06-02at22_19_18.thumb.png.1dd945d155a0e8629c140e0f17745ba5.png

callback.fmp12

Share this post


Link to post
  • 0

Maar het is nog niet echt wat ik zoek, hoewel het al heel goed en bruikbaar is natuurlijk.

Ik zoek methodes waar het javascript braafjes in standby blijft zolang er een script "hangende" is. Soms is het kritisch dat een script eerst uitgevoerd wordt, vooraleer de webviewer interface terug beschikbaar wordt. Dat gebeurd met deze methodes niet.

Dit was mijn oorspronkelijke vraag ( in de andere thread ) :

 

  1. script word opgeroepen met FileMaker.DoScript()
  2. code gaat in pause ( met timeout ) en wacht op een variabele die vanuit FileMaker gezet wordt.
  3. code gaat verder nadat het resultaat gezet is of timeout bereikt is

Laten we het "synchroon" programmeren noemen... :-) en misschien is javascript hier niet zo happy mee, maar zo wil ik het wel.

Share this post


Link to post
  • 0
Posted (edited)

er is geen pauze in JavaScript... dat is het hele punt net van asynchroon programmeren: doe dit, als het goed gaat doe dan dat. Daarom dat Promises er zijn gekomen, omdat je dan overzichtelijk kan programmeren zonder in de callback hell terecht te komen.

 

$("GET","/myData")
   .then ( ( data ) => fm.performScript( "SaveData" , data ) )
   .then ( ( fmScriptResult ) => alert( "data has been saved:"  + fmScriptResult ) )
   .catch ( alert ( "something went wrong" ) );

"data has been saved" zal pas aangeroepen worden als filemaker klaar is met zijn functie. De code "wacht" dus wel om uitgevoerd te worden. Of beter gezegd: de code staat geregistreerd om uitgevoerd te worden, indien filemaker zegt dat alles goed is gegaan en dat de rest mag uitgevoerd worden.

Dit lijkt mij de juistere (of in ieder geval meer Javascript gerichte) manier van werken, ipv een valse pause te gaan maken (want je code loopt wel verder, alleen zit die in een continue loop).

Edited by andries

Share this post


Link to post
  • 0

Ja, alles klopt wat je zegt, maar wat gebeurt er als er een bug in het script is, en het script nooit antwoord? Het script wordt verondersteld klaar te zijn in bijvoorbeeld 3 seconden max.

Je merkt voor de 2de keer op dat dit de javascript gerichte manier van werken is. Fijn, maar ik wil wel de webviewer me vertelt dat FileMaker niet teruggekomen is met een antwoord.

Dus nog eens ter verduidelijking:

  1. script call vanuit de web viewer - > FileMaker
  2. wacht op antwoord in web viewer
  3. zodra antwoord volgende actie in web viewer, indien geen antwoord, bail out
  4. volgende stap in web viewer

Mijn doel is niet om asynchroon te programmeren, maar om iets gedaan te krijgen met 100% zekerheid. Als dat kan met asynchroon programmeren, goed. Iedereen happy. Als dat niet kan op een javascript gerichte manier, en er is een "valse pauze" voor nodig, is dat voor mij ook OK, zolang de CPU niet opgevreten wordt in de wacht loop.

Share this post


Link to post
  • 0

Dit doet al meer waar ik naar zoek:

Het javascript roept een FileMaker script op, en wacht dan op een reply. Ik had even wat research nodig om dat op een asynchrone manier te doen.

In de code staat de timeout op 10 seconden, dus ofwél is het javascript happy omdat het een resultaat terug kreeg, ofwel krijg je een javascript dialoog waarin staat dat het te lang duurde.

<html><head>
<script>
//library
var FileMakerWrapper = function() {
	this._promises = { } ; this._promiseCounter = -1 ;
	this.performScript = function( script, paramString , callBackFunction ) {
		this._promiseCounter = this._promiseCounter + 1 ; var promiseID  = this._promiseCounter ;
		var scriptParameters = { id: promiseID, param:paramString } ;
		try { FileMaker.PerformScript( script, JSON.stringify( scriptParameters ) ) } 
		catch ( e ) { alert ( "Fout: FileMaker.PerformScript() werkte niet." ) } 
		this._promises[ promiseID ] = { callBack: callBackFunction, done: false } ;
		return promiseID ;
	}
	this.checkScriptDone = function( promiseID ) {
		return this._promises[ promiseID ].done ;
	}
	this._resolvePromise = function( promiseID, resultForCallBack ) {
		this._promises[ promiseID ].done = true ;
		this._promises[ promiseID ].callBack ( resultForCallBack ) ;
	}
};
try { var fm = new FileMakerWrapper() } catch ( e ) { alert( "FileMakerWrapper kon niet aangemaakt worden." ) ; }

function fmcallback( param ) {
	let scriptResults = JSON.parse( param );
	try { fm._resolvePromise( scriptResults.id, scriptResults ) } catch (e) { alert( e ) }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function waitForScript ( scriptID, timeOut ) {
	delay = 0 ; incr = 100 ;
	var done = false
	while ( ( done == false ) && ( delay < timeOut ) ) {
		done = fm.checkScriptDone ( scriptID ) ;
		document.getElementById("poll").value = 'done:' + done + ' ' +  delay + ' ms' ; 
		delay = delay + incr ;
		await sleep ( incr ) ;	
	}
	if ( ! done ) { alert ( 'timeout waiting for script' ) } ;
}
</script>

<script>
//application
function SaveRecord() {
	var scriptID = fm.performScript ( "SAVE RECORD", "",
		function( SaveResult ) {
			alert( JSON.stringify( SaveResult ) ) ;
			document.getElementById("saveResult.msg").value = SaveResult.msg ; 
			document.getElementById("saveResult.error").value = SaveResult.error ; 
		}
	) ;
	waitForScript( scriptID, 10000 ) ;
}
</script>
</head>
<body>
<div><button onclick="SaveRecord()">Bewaar Record</button></div>
<div><input id="saveResult.msg" type="text" value=""</div>
<div><input id="saveResult.error" type="text" value=""</div>
<div><input id="poll" type="text" value=""</div>
</body>

 

callback.fmp12

Share this post


Link to post
  • 0

top! hoewel ik het niet snap waarom je daar zo hard aan vasthoudt, eender welke integratie dat je ooit wil gaan doen zal met callbacks werken en niet met pauzes.

 

persoonlijk ben ik dan van zoiets meer fan om de performscript nog wat uit te breiden met een timeout, die dan een error gooit als timeout bereikt is.

fm.performscript ( "myscript", "myparam", timeout )
    .then ( () => alert("filemaker script called" ) )
    .catch( () => alert("timeout") )

 

Share this post


Link to post
  • 0
20 minuten geleden zei andries:

top! hoewel ik het niet snap waarom je daar zo hard aan vasthoudt, eender welke integratie dat je ooit wil gaan doen zal met callbacks werken en niet met pauzes.

Er is nog altijd een callback maar met deze code kan je ook opvangen dat het script fout loopt en niks terugstuurt.

Dat is verschillend van wat je aanstipt. Je krijgt alleen een fout als het FileMaker script niet kan gestart worden. Ik vang op dat het script wel gestart wordt, maar niet antwoordt.

Share this post


Link to post
  • 0

Ik ben ook niet vloeiend genoeg met javascript om uit te vinden hoe je een gebruikte promise terug wist. Kan je me zeggen hoe je die weg krijgt?

Een ander dingejte is dat het javascript een vlag moet hebben dat er een script call naar FIleMaker gestuurd is. Javascript is dan wel asynchroon bezig, maar FileMaker niet, en we willen geen script call naar FileMaker sturen als er een script bezig is.

Share this post


Link to post
  • 0

Gesprekje gehad op de dotfmp hierover met jou, er is me 1 en ander duidelijk nu, maar moet hier nog even over nadenken. Bedankt in elk geval.

Share this post


Link to post
  • 0

Gisteren als oefening fullcalendar beginnen te integreren met FileMaker 19. https://www.clarify.net/wvr/fullcalendar/fullcalendar.html geeft je een blanco pagina, maar dat het is ook niet bedoeling dat je dit vanuit je browser opent.

Mijn insteek was als volgt:

  • Begonnnen met Carafe, en een standaard deploy
  • Dan beginnen kijken naar de code, en beslist dat ik alle includes zou verhuizen naar https://www.clarify.net/wvr/fullcalendar, gewoon omdat ik er dan goed bij kon, en omdat ik altijd wat last heb met code die beheerd wordt door iemand anders :-)
  • De html verhuis ik tijdens mijn tests naar https://www.clarify.net/wvr/fullcalendar/fullcalendar.html , da's gewoon gemakkelijker dan javascript te zitten wijzigen in FileMaker zélf, en ik kan er gewoon goed aan door die web dir te mappen via smb. BBEdit is niet de beste javascript editor maar ik gebruik 'm als zó lang en dit gaat zeker beter dan in een FileMaker veld te zitten prutsen.
  • Eerste dingetje dat ik deed was een wacht loop inbouwen zodat het FileMaker object kon laden, Russel ( Mr. Watson ) praatte daarover eergisteren in zijn "getting up to speed" sessie, en het was heel belangrijk, jij refereert er ook naar hierboven.
  • Dan een bypass naar een functie loaded() die awaitFileMaker(100) doet, dus 100 milliseconden wacht, en indien FileMaker object laadt ( dus alleen in webviewer ) gaat het FileMaker.PerformScript ( "init calendar" ) uitvoeren, waarop dat FileMaker script de configuratie parameters doorstuurt naar loadCalendar(). Hier dus nog niet jouw wrapper lib gebruikt.
  • Vervolgens loadCalendar aangepast zodat het de kalender initialiseert met de data parameters die we via het script doorgaven: defaultDate, firstDay, editable, enz. echter GEEN events. Initieel wél, maar later heb ik dat veranderd.
  • Ik kon nu het Carafé script volledig laten vallen, en gewoon de URL oproepen, dus al de substitute functies en zo waren niet meer nodig. Cool.
  • Vervolgens wat meer parameters beginnen doorgeven voor de initialisatie, het leek me leuk dat je in FileMaker bepaalt hoe alles er moet uitzien, en niet in de html. Ook de fmp urls eruit gehaald en vervangen door rechtstreekse FileMaker.PerformScript calls, nog steeds jouw wrapper lib niet gebruikt.
  • Knopje voor volgende en vorige maand toegevoegd, en dit confronteerde mij met de event feed, die ik tot hiertoe gewoon voor de maand van de default date had gemaakt. En die natuurlijk niet zomaar events gaat ophalen bij navigatie… Events moesten dus op een andere manier binnenlopen dan bij initialisatie, tijd om wat verder in de fullcalendar docs te kruipen, en de volgende uren heb ik daar ook wel wat tijd gespendeerd. Mede omdat ik in de verkeerde versie van de docs zat (v4), Carafe voorziet dus een oude versie van fullcalendar.
  • Even met v4 gespeeld, maar toen zag ik dat de dingen nog meer braken, dus terug naar v3. In de documentatie https://fullcalendar.io/docs/v3/events-function vond ik een manier om events op te halen via een functie. Dat was events: function( start, end, timezone, callback ) { },  fullcalendar gaat dus zélf beslissen wanneer het data nodig heeft, en FileMaker moet alleen een JSON reply geven voor een gegeven start- en einddatum.
  • En hier komt-ie… nu zie ik waarom @andries die gekke structuur "data callback / error callback" gebruikt, want het maakt ook deel uit van de javascript cultuur. Sorry voor mijn eerder onbegrip, ik ben een beetje gehinderd door jaren code schrijven zonder callbacks. fullcalendar gebruikt weliswaar alleen de success callback, maar in v4 gebruiken ze ook de error callback. Hier dus de event feed die door de wrapper van Andries ongelooflijk elegant wordt:
events: function ( start, end, timezone, callback ) {
 var param = {} ; param.start = start ; param.end = end ; param = JSON.stringify( param ) ;
 fm.performScript("[web] fetch events", param )
  .then ( function ( data ) { callback ( JSON.parse ( data ) ) } )
  .catch( function ( error ) { alert("ERROR:" + error ) } ) 
},

Het is nog niet af, ik denk dat ik alles nu via de fmwapper lib ga laten lopen, dan van ik ook de errors op. Ook tijd voor een design beslissing: events editen via de webviewer en doorgeven aan FIleMaker, of events doorgeven aan FileMaker, daar wijzigen en terug doorgeven aan de webviewer?

Er is ook nog iets mis met die awaitFileMaker() routine, ik ga niet 100% OK met die Promise om, Safari is in elk geval niet happy als ik de pagina op die manier laadt.

Share this post


Link to post
  • 0

haha, net het voorbeeld dat ik ga geven zo meteen op dotfmp :)

ik heb verder de await nog eens onderzocht, but as usual: filemaker implements dinosaurs... so not supported in webviewer :)

Share this post


Link to post
  • 0

een andere voorbeeld in fullcalendar is de drop event. waar je een info object binnenkrijgt met een revert functie (hun error functie dus). en dan kan je doen.

fm.performScript ( "update record", event ).catch ( (error ) = > info.revert() );

waar info.revert() is de functie die je kreeg van fullcalendar die je moet aanroepen als het opslaan misloopt.

Share this post


Link to post
  • 0

Ja die revert is waar het echt interessant wordt, event kan niet verplaatst worden nu omdat het record gelocked is in FileMaker, en revert dus terug. Werkt als een speer.

Anyway Russel en ik hebben het Get ( ScriptResult ) aan de FileMaker kant helemaal werkend! Je kan dus nu een perform script in webviewer doen, en het scriptresultaat terug krijgen, terwijl je script gewoon blijft verder draaien:

Perform Script [ module.doWebViewerScript , parameters: blabla ]

set variable #result [ Get ( ScriptResult ]

Waarschijnlijk had jij een beetje pech dat het niet werkte toen je het probeerde, want het werkt echt.

Als je tijd hebt en je kan je bibliotheek verder completeren met "waitForFileMaker" en "scriptTimeouts" zullen Russel en ik de module verder afwerken en op github zetten.

Share this post


Link to post
  • 0

ik vrees dat we er niet gaan geraken. want await kan enkel binnen een asynchrone functie.... wat dus wil zeggen dat mijn functie eerst en vooral dus al asynchroon moet zijn... en dus binnen een asynchrone functie kan ik synchroon wachten op een andere asynchrone functie... begrijpen wie begrijpen kan :)

Share this post


Link to post
  • 0

het kan dus wel, als je de functie die filemaker.performscript in een async gooit.

 

async function callFileMaker(){
	let result = await FileMakerWrapper.performScript ( "SaveRecord" , $record );
    alert(result);
}

callFileMaker();

 

Share this post


Link to post
  • 0

Status update: we zijn er ondertussen helemaal technisch uit, en deze thread resulteerde in een ongelooflijk handig tooltje. Samen met Mr. Watson en Andries gooiden we alle ideeën en code bij mekaar, en we hebben dit donderdagavond gepresenteerd in de dotfmp 1/12 video meeting.

Geheel onverwacht waren er een aantal mensen helemaal niet blij met wat we deden. Ik besefte niet goed wat er gebeurde eigenlijk. Er werd betwijfeld of dit wel open source moest zijn en zo. Andere mensen hadden het over werknemers die niet zomaar know-how mogen delen - dit terwijl het over een paar regels JavaScript code gaat die al jaren geleden in de community gedeeld zijn - en dat was toen helemaal geen probleem omdat niemand het nut ervan in zag.

Achteraf, en na wat dialoog met Andries en een andere vriend, is het me duidelijk dat heel wat mensen Open Source perfect OK vinden, zolang het maar geen pad kruist met hun eigen commercieel werk. Dat is in mijn opinie kortzichtig. En men wilt wel iets delen, maar verwacht daar altijd "eternal fame & glory" voor, een soort van marketing tool voor je business. Als Open Source concurrentie wordt, moet je gewoon creatiever worden dan je bent. Open Source werkt aan twee kanten. En als je alleen deelt als iedereen het kan zien, dan ben je eigenlijk gewoon een egoist.

Niemand heeft een probleem dat bijvoorbeeld fullcalendar ( https://fullcalendar.io/license ) een Open Source project is, en niemand heeft er een probleem mee om die code ook in hun commerciele oplossingen te steken. Maar het is voor sommigen wél een probleem als we nieuwe FileMaker functionaliteit onderzoeken, problemen identificeren en oplossingen zoeken, en die know-how met iedereen willen delen. Een beetje absurd eigenlijk.

Resultaat: er gebeurd nu voorlopig even niks. Ik moet dit even laten bezinken, en ik probeer de laatste dagen controle te krijgen over een boosheid waar ik niet vanaf geraak. Een video meeting is een goed alternatief op een IRL meeting, maar een muur van intialen waarbij iedereen de camera en micro afzet, en een paar mensen nemen je op de korrel zonder dat iemand reageert, ik kan je vertellen dat dat geen fijne ervaring is.

Share this post


Link to post
  • 0

Dat is één van de redenen waarom het zelfstandig worden mij zo goed is bekomen. Er is niemand die mij zou kunnen bekritiseren om het beschikbaar stellen van mijn ideeën en eventuele code die er bij hoort. Niet dat mij zoiets ooit is gebeurd, maar ik heb er wél altijd rekening mee gehouden.

De kracht van gedeelde ideeën is dat anderen op jouw aangeven óók op ideeën komen en die vaak óók weer met jou delen. Voordat je het weet zit je ineens met een oplossing die beter werkt dan dat je in je eentje zou hebben bedacht!

Het gevolg van die betere oplossing is dat de klant wel eens nog blijer met jouw oplossing zou kunnen zijn dan hij voorheen al was. Jij zou misschien minder problemen moeten hoeven oplossen en de klant komt dan met extra opdrachten en of beveelt je zomaaar aan bij anderen. Voordat je het weet zit je agenda vol met betaald werk en verdien je een betere boterham dan voorheen.

Dus Peter, wees alsjeblieft niet boos op die mensen. Zij denken, zoals jij zelf al opmerkte, alleen op de korte termijn en hun eigen portomonaie-tje.

Share this post


Link to post
  • 0

Bedankt Menno, de boosheid is momenteel een beetje aan het overslaan in onverschilligheid. Misschien is dat ook niet goed, maar ik heb er in elk geval minder last van.

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...