Use of UpdateHandler

981 views
Skip to first unread message

David Vermeir

unread,
Mar 24, 2014, 9:43:59 AM3/24/14
to keyst...@googlegroups.com
When browsing through the SydJs code I noticed that the UpdateHandler class is used to save changes to Model instances outside of the Admin UI. 
In the 'Me' route for example, there's no logic for saving the profile. How does this work? Does the UpdateHandler somehow use the saving/validation/... logic that get's used when editing a record through the Admin UI?

j...@keystonejs.com

unread,
Mar 24, 2014, 9:53:13 AM3/24/14
to keyst...@googlegroups.com
It sure does, the UpdateHandler is actually exactly what the Admin UI uses for creating and saving data. It also wraps the logic for uploading images / etc.

As for how it knows what to validate / which fields to look for - it uses the models you've created.

The idea is to make it possible to use it throughout the project, and reduce repeat code as much as possible.

It's got a fields option you can set to white-list the fields you want to be able to be changed through the front end (highly recommended) and the ability to specify a custom list of required fields to validate.

It can log errors to the console and as flash messages, and passes back an object with details if an error occurs (e.g. to display required fields with a different class if validation failed). So unless you're trying to do something really custom, it should do the trick.

Unfortunately it's not well documented outside of the code, I'll try and get around to that soon :)

David Vermeir

unread,
Mar 24, 2014, 11:01:21 AM3/24/14
to keyst...@googlegroups.com
Hey Jed,

I refactored my code to use the updateHandler and it works great! Very simple to use and yet incredibly powerful. The only thing I can't get to work is an cloudinary image upload field, like the one on the SydJS website.

Cheers,
David

j...@keystonejs.com

unread,
Mar 24, 2014, 11:13:05 AM3/24/14
to keyst...@googlegroups.com
Remember to put enctype='multipart/form-data' on your form, and call the image field {path}_upload.

Check the forum site for another working example:

David Vermeir

unread,
Mar 25, 2014, 4:49:16 AM3/25/14
to keyst...@googlegroups.com
Thx, that was the problem! :)

Gregor Elke

unread,
Mar 25, 2014, 5:44:06 AM3/25/14
to keyst...@googlegroups.com
the documentation on that part is what i miss most from the docs currently, since most examples are working with that.

Simon Morgan

unread,
Dec 22, 2015, 1:26:28 PM12/22/15
to Keystone JS
Hello,

I'm new to keystone and I'm trying to upload an image to S3 from a front end form, works OK from the admin UI.

Where do I specify the {path}_upload ? In the model? and is {path} meant to be replaced with the S3 path?

Thanks
Simon

Simon Morgan

unread,
Dec 23, 2015, 5:26:21 AM12/23/15
to Keystone JS
All good - I figured it out now.

Chris Troutner

unread,
Dec 23, 2015, 2:52:43 PM12/23/15
to Keystone JS
For future reference, I wrote up two tutorials on uploading images via the KeystoneJS API:


joes.mail...@gmail.com

unread,
Feb 5, 2016, 2:18:42 PM2/5/16
to Keystone JS
Hi,

Is there anything special that needs to be done in this regard when trying to update a RelationshipType through the 'Me' route (or any client / non-AdminUI route)? 

I have created a RelationshipType key (named 'language', with many: true) associated with my User model, which I am able to successfully display and modify in the Me page as a react-select component, just like in the AdminUI.  When I submit a change however, it never updates the relationship item, but it does succeed and displays the message "Your changes have been saved."

If I make the change in the AdminUI instead, then everything is correctly updated.
I have added the key 'language' to the 'fields' whitelist, and confirmed that if I create the 'language' model as an optional String type key in the User model profile section, that updates work in both the client/frontend and the AdminUI/backend. 

I have spent the day tracking this through the backend, and see that the changes are passed correctly as a list of relationship ObjectIds (e.g. 56ae2bf23bebedcd2fa8ffb0,56ae2c5c3bebedcd2fa8ffb4,56ae2c413bebedcd2fa8ffb3) all the way to

RelationshipType.js: relationship.prototype.updateItem

where I can see that the following also succeeds:
if (!_.isEqual(_old, _new)) {
        console.log ("_new_compact: " + _new);  <-- log temporarily added by me
        item.set(this.path, _new);
}


Everything appears to work!
But nothing is updated in the model, and when the page refreshes the Relationship items are all exactly as they were before I tried to make a change.

I cannot seem to find the place in the code where 'item.set (this.path, _new);' is defined, so now I feel stumped.
If I make the same changes in the AdminUI however, again everything is actually updated, and those changes are also reflected in the Me route the next time I visit.

I have also tried examining the POST content and comparing the AdminUI and Me frontend page, but they appear identical.

I noticed a cryptic error message in the source of RelationshipType.js:
        if (item.populated(this.path)) {
                throw new Error('fieldTypes.relationship.updateItem() Error - You cannot update populated relationships.');
        }
however this does not appear to be getting fired so I guess it is irrelevant.

If this rings any obvious bells hints would be greatly appreciated!

joes.mail...@gmail.com

unread,
Feb 7, 2016, 3:01:28 PM2/7/16
to Keystone JS
Hi,

  I eventually managed to resolve this but I'm not 100% sure I understand why.

  Initially I had implemented the following query in the view.on('init', function(next) {... of /routes/views/me.js.
----------------------
        User.model.findOne ()
            .where ('key', locals.user.key)
            .populate ("language")
            .exec (function (err, languages) {
                if (languages) {
                    locals.user.language = languages.language
                } else {
                    console.log ("NOTHING");
                }
        });
----------------------


  This seemed to create some kind of race condition where occasionally, but infrequently, the mongoose updates would actually contain the correct, updated data, and it would be committed correctly to mongodb.  Most of the time however, it just 'succeeded' with the query, but using the original data - despite the logging info indicating otherwise (see my initial post).  In effect, nothing changed.

  After reading more of the code and documentation, I tried moving this logic into /routes/middleware.js :
----------------------
/**
        Make languages universally available
*/

exports.loadLanguages = function(req, res, next) {
    keystone.list('Language').model.find().exec(function(err, languages) {
        if (err) return next(err);
        req.languages = languages;
        res.locals.languages = languages;
        next ();
    });
}
----------------------

and then calling this from /routes/index.js
----------------------
keystone.pre('routes', middleware.loadLanguages);

----------------------

Then I load this into an AppData global in the following the SydJS site example:  AppData.langauges = !{JSON.stringify(languages)}; where it can be accessed by my React component and loaded into react-select.

After this, there appear to no longer be any race conditions, and both submission/update and display work consistently in the sense that changes are correctly sent, correctly pushed to mongo, and persisted across the app.

This is probably not the most optimal solution - especially since there is not really a need to make this data available globally, but I wanted to share it, if only to possibly receive an explanation about the race condition, and maybe save someone else a bit of time if they stumble upon similar behavior!


 

joes.mail...@gmail.com

unread,
Feb 7, 2016, 3:04:49 PM2/7/16
to Keystone JS
Minor edit:
 
The initial bit of code from /routes/views/me.js

Should have read:
----------------------
        Language.model.find ()

            .exec (function (err, languages) {
                if (languages) {
                    locals.languages = languages;
                } else {
                    console.log (err);
                }
            });

        User.model.findOne ()
            .where ('key', locals.user.key)
            .populate ("language")
            .exec (function (err, languages) {
                if (languages) {
                    locals.user.language = languages.language
                } else {
                    console.log ("NOTHING");
                }
        });
----------------------

(load the list of languages, AND 'populate' the relationship values for the languages registered to the User model.

Cheers!

Ryan Cole

unread,
Jul 5, 2016, 7:18:05 AM7/5/16
to Keystone JS
A simple code example of how to use the UpdateHandler would be super appreciated. The source code is not entirely clear what is expected in terms of arguments, or how responses are dealt with. I feel like this is a powerful feature which I'm just not able to confidently implement due to missing documentation. :(

Suhail Najmi

unread,
Oct 16, 2018, 12:56:06 AM10/16/18
to Keystone JS
Same here! :(

Suhail Najmi

unread,
Oct 19, 2018, 2:37:07 AM10/19/18
to Keystone JS
    user.model.findById(req.params.id).exec(function(err,item){

      // ERROR THROW UPON QUERYING PROBLEM
      if(err)
        throw new Error("Error in querying a user : " + err);

      // CREATE NEW INSTANCE OF UPDATE HANDLER
      var updateHandler = new keystone.updateHandler(user, item, req, {});

      // PROCESSES THE UPDATE (Just like updateItem)
      updateHandler.process(req.body, {
        flashErrors: true,
        logErrors: true,
        files: req.files,
        user: req.user
  }, function(err) {
        if (err) {
          console.log("An error happened!");
          console.log(err);
          locals.data.validationErrors = err.errors;
        } else {
          req.flash('success', 'Account updated!');
          return res.redirect('/dashboard/' + req.params.id);
        }
        next();
      });
    });

Here's a handy example for you, hopefully it helps. If you still can't understand, just pm me... :)
Reply all
Reply to author
Forward
0 new messages