These are chat archives for canjs/canjs

21st
Aug 2018
Mohamed Cherif Bouchelaghem
@cherifGsoul
Aug 21 2018 00:09
@pYr0x yes the first one is correct
Julian
@pYr0x
Aug 21 2018 09:55
@cherifGsoul why the second one not?
Mohamed Cherif Bouchelaghem
@cherifGsoul
Aug 21 2018 09:56
@pYr0x you want to initialize it with default value, right?
Julian
@pYr0x
Aug 21 2018 09:58
i wrote two issues. the first one is that select issue. that gets solved with :point_up: 20. August 2018 20:43
the second issue is , working with data thats gets resolved with promises
i want to edit the data in a form thats gets resolved by a promise
and i want to know if the first or the second one is the correct way to get the data from the promise
Mohamed Cherif Bouchelaghem
@cherifGsoul
Aug 21 2018 10:02
the two are correct the first one initialize with default value
Just using this inside the handler may cause troubles
data: {
  value: function (prop) {
    prop.listenTo("dataPromise", function (ev, newPromise) {
      newPromise.then((response) => {
        if (response) {
          prop.resolve(response);
        }
      });
    });
  }
}
Julian
@pYr0x
Aug 21 2018 10:08
whats the difference between this two methods? (my first and second one)
both set the data to this.data
but in a different way
Mohamed Cherif Bouchelaghem
@cherifGsoul
Aug 21 2018 10:10
I see is the same way just this line prop.listenTo(prop.lastSet, prop.resolve); sets a default value
Jeroen Cornelissen
@jeroencornelissen
Aug 21 2018 10:14
@pYr0x @cherifGsoul I prefer the second.
You can set a default value by resolving it outside the listenTo.
data: {
  value: function (prop) {
    prop.resolve([]); // set a default value
    prop.listenTo("dataPromise", function (ev, newPromise) {
      newPromise.then((response) => {
        if (response) {
          prop.resolve(response);
        }
      });
    });
  }
}
Justin Meyer
@justinbmeyer
Aug 21 2018 13:52
I think of prop.listenTo(prop.lastSet, prop.resolve); as allowing someone to set the property to a value, it doesn't really mean "default"
a default value is more like when you resolve right away
myProp: {
  value({resolve}) {
    resolve(3) //-> 3 is the default value
  }
}
like @jeroencornelissen I would call resolve()
setting this.data creates extra levels of miss direction
gregorgodoy
@gregorgodoy
Aug 21 2018 14:27
Hi all! I have a model with a natural primary key, this means the id is defined in the client. For simplicity I will try to explain it with an example. Lets say I want to register cars from a park lot. Each car has a plate, this plate is unique and this plate code/number will never change to another car. So, its a perfect natural primary key candidate. The API endpoint defines plate_code as the primary key. In the car model I defined the QueryLogic's identity to plate_code. The car's plate code is filled in a form by the user and when I call save it always call the updateData, because plate_code is allready defined, but not created yet. How to solve this? I can imagine a lot of API endpoint that could use a natural pk, for example: invoice -> invoice_number (some countries use pre-printed invoices, with number already assigned, where the user must match the pre-printed number), product -> product_code where for example the company has his own nomenclature for product codes. How to handle this cases where the identity values are filled by the user in the app before they exists and should fire the createData method instead of updateData?
Julian
@pYr0x
Aug 21 2018 14:27
but with resolve the data will not be set. you told me, that resolve do not set ... resolve is like "what flows out"
Justin Meyer
@justinbmeyer
Aug 21 2018 14:31
@pYr0x resolve returns what is read ... how is that changing things for you?
@gregorgodoy you might be able to change your isNew() function
to detect "new-ness" by other properties
Julian
@pYr0x
Aug 21 2018 14:32
i check for "lastSet"
and it is undefined because i not set it with resolve
Justin Meyer
@justinbmeyer
Aug 21 2018 14:33
ok, that's fine though
or, I should say, what's the problem with that
most value() functions don't use lastSet
I view it as a source of events
used to derive the final value
it's not the final value
gregorgodoy
@gregorgodoy
Aug 21 2018 14:39

@justinbmeyer Interesting, is there a property that only exists if the instance was retrieve from the API? In may case I am using realtimeRestModel I thought about changing the isNew() function but i also saw that the save funtion in can-connect/constructor/constructor.js is evaluating if id is undefined or not, instead of isNew() and I was not sure if I changed the isNew function it will affect the save:

save: function(instance){

            var serialized = this.serializeInstance(instance);
            var id = this.id(instance);
            var self = this;
            if(id === undefined) {
                // If `id` is undefined, we are creating this instance.
                // It should be given a local id and temporarily added to the cidStore
                // so other hooks can get back the instance that's being created.
                var cid = this._cid++;
                // cid is really a token to be able to reference this transaction.
                this.cidStore.addReference(cid, instance);

                // Call the data layer.
                // If the data returned is undefined, don't call `createdInstance`
                return this.createData(serialized, cid).then(function(data){
                    // if undefined is returned, this can't be created, or someone has taken care of it
                    if(data !== undefined) {
                        self.createdInstance(instance, data);
                    }
                    self.cidStore.deleteReference(cid, instance);
                    return instance;
                });
            } else {
                return this.updateData(serialized).then(function(data){
                    if(data !== undefined) {
                        self.updatedInstance(instance, data);
                    }
                    return instance;
                });
            }
        },

How can I extend or overwrite the isNew function without modifying the original one?

Mohamed Cherif Bouchelaghem
@cherifGsoul
Aug 21 2018 14:44
@justinbmeyer thanks for the clarification!
Justin Meyer
@justinbmeyer
Aug 21 2018 14:49
@gregorgodoy we should probably change .save() then
hmmm (thinking of other ways of solving this)
one way (though not great), would be to make all responses add some sort of additional property that could be used to signal that the data has been created
example:
parseInstanceData(data){
  data.createdKey = data.license;
  return data;
}
gregorgodoy
@gregorgodoy
Aug 21 2018 14:54

@justinbmeyer Ok, thanks. I also saw that addInstanceReference uses if (id===undefined):

addInstanceReference: function(instance, id) {
            var ID = id || this.id(instance);
            if(ID === undefined) {
                // save in the newInstanceStore store temporarily.
                this.newInstanceStore.addReference(instance);
            } else {
                this.instanceStore.addReference( ID, instance );
            }

        },

and many other functions inside can-connect/constructor/store use this same logic. This affects me because im using realtimeRestModel.

Justin Meyer
@justinbmeyer
Aug 21 2018 14:55
I'd try that parseInstanceData() trick
gregorgodoy
@gregorgodoy
Aug 21 2018 14:55
@justinbmeyer Adding the extra property should work!!
Justin Meyer
@justinbmeyer
Aug 21 2018 14:55
b/c you need some sort of way to identify the data that came back from the server from the data you are creating
if it works, could you write up something short in the forums?
gregorgodoy
@gregorgodoy
Aug 21 2018 14:57
Sure!! thanks for the suggestion!! This should work for lists too? What about long lists? 100 or 1000 rows? any possible memory leak?
I also have control over the API, I was considering to add an dummy id to each endpoint. But I really want to avoid this. I think is not a good practice for the database to maintain a extra index that really doesn't has any meaning
gregorgodoy
@gregorgodoy
Aug 21 2018 15:24
@justinbmeyer Following your example I should declare createdKey as the identity, right?
Justin Meyer
@justinbmeyer
Aug 21 2018 15:58
yes
What are your concerns about lists @gregorgodoy ?
Lists use the "query" as their key
gregorgodoy
@gregorgodoy
Aug 21 2018 16:03

Ok, but ill need to modify each url, because createdKey is not the real Identity

url: {
  getListData: "GET /api/todos/find",
  getData: "GET /api/todo/get/{id}",
  createData: "POST /api/todo/create",
  updateData: "POST /api/todo/update?id={id}",
  destroyData: "POST /api/todo/delete?id={id}"
}

the updateData would set createdKey value as the id. I could specify a function returning the Promise.

gregorgodoy
@gregorgodoy
Aug 21 2018 16:12

I already did that, but in my case updateData is a generic function that reads the identity value and creates dynamically the url endpoint. I created a custom myRestModel module, that takes 2 parameters: the DefineMap and the QueryLogic. My model actually looks like this:

import { DefineMap, DefineList, QueryLogic } from 'can';
import myRestModel from '~/models/my-rest-model';

const Invoice = DefineMap.extend("Invoice",{
    seal: false
}, {
    'invoice_number': {
        type: 'string',
        default: '001-001'
    }
});

Invoice.List = DefineList.extend({
    '#': Invoice
});

Invoice.connection = myRestModel(
    Invoice,
    new QueryLogic({
        identity: ['invoice_number']
    })
);

export default Invoice;

And myRestModel looks like this:

import {realtimeRestModel,namespace,ajax} from 'can';

const myRestModel = function(Model,queryLogic,options) {

    var url = '/api/'+Model.shortName.toLowerCase();

    var connection = realtimeRestModel({
        url: {

            updateData: function(param) {

                var ids = queryLogic.identityKeys();
                var idQuery = [];
                for (const i in ids) {
                    idQuery.push(`${ids[i]}=eq.${param[ids[i]]}`);
                }
                idQuery = idQuery.join('&');

                return new Promise(function(resolve, reject){ 

                    $.ajax({
                        url: `${url}?${idQuery}`,
                        type: 'PATCH',
                        dataType: 'json',
                        data: param,
                        beforeSend: function(xhr,settings) {

                            xhr.setRequestHeader('Prefer', 'return=representation');
                            xhr.setRequestHeader('Accept', 'application/vnd.pgrst.object+json');

                            return xhr;
                        }
                    }).then(resolve, reject);

                });

            }
        },
        Map: Model,
        updateInstanceWithAssignDeep: true,
        List: Model.List,
        queryLogic: queryLogic
    });


    return connection

}

export default myRestModel;

That way, each model url endpoint are build dynamically. Now, if I change the Identity property I have to write each Url Promise function for each model.

About the lists concern, I misunderstood it, sorry.
gregorgodoy
@gregorgodoy
Aug 21 2018 16:21
As the API needs some custom headers, I wrote a function for each url returning the promises. I want to avoid to repeat the same code on each model. For that reason I wrote myRestModel that reads the identityKeys and builds the url and adds this custom headers. If I change the identity keys to createdKey this logic will have no sense anymore. I could pass a third parameter options that contains the "real identity" to build dynamically the url endpoints.

For exmaple:

Invoice.connection = myRestModel(
    Invoice,
    new QueryLogic({
        identity: ['createdKey']
    }),
    ids: ['invoice_number']
);

and then in myRestModel instead of reading identityKeys like this: var ids = queryLogic.identityKeys(); I could read them from options, like this: var ids = options.ids;

gregorgodoy
@gregorgodoy
Aug 21 2018 16:27
When I do queryLogic.identityKeys() I receive a warning:
can-query-logic.js:103 you probably can get the identity keys some other way
@justinbmeyer What would you suggest? Thanks
Julian
@pYr0x
Aug 21 2018 17:55
what is the prefered way: class="{{menu}}" or el:className:from="menu"
gregorgodoy
@gregorgodoy
Aug 21 2018 18:54
@justinbmeyer I think I found the solution for my escenario. Ill post it later in the forum, thanks for the guidance!
Kevin Phillips
@phillipskevin
Aug 21 2018 20:01
@pYr0x I normally use class="{{menu}}"
Justin Meyer
@justinbmeyer
Aug 21 2018 22:40
@gregorgodoy ignore that. I need to remove that warning.