"Edit Mode" button

PeterPeter Member
edited June 29 in Ideas
I'd like a button on any field editors, tables, or headers/titles that allows the user to put the section into full edit mode. I like my pages to default to read mode, but the user should be able to put the page into edit mode when they want to, similar to standard SF UI.
Tagged:
4
4 votes

Implemented · Last Updated

«1

Comments

  • Anna WiersemaAnna Wiersema Skuid Mod, Admin 🛠️ 
    edited December 2019
    So the best way to do this is to create the edit version of your page using Skuid and then create a redirect action on the view page that will take users to the edit page. The redirect URL for the edit aciton is just /{{Id}}/e. You can also check this tutorial for step by step instructions about creating an edit page. Is this what you're looking for?
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited June 2019
    Actually, Anna, I can do you one better. I just did this for another client of ours. Peter, in lieu of this being officially implemented, you could use the following snippet to manually switch a field editor's state (you'd just need to create the button to fire this snippet off).
    var $ = skuid.$;  // check for "global" toggle state variable  // if it doesn't exist, create it  if(!window.blEditorMode) window.blEditorMode = 'read';    // this will grab any visible field editor  // you could also do the same with tables by adding   // ", .nx-skootable" to the selector  var fieldeditor = $('.nx-basicfieldeditor:visible').data('object');    if(window.blEditorMode == 'read') {        // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-pencil').each(function() {          $(this).find('.ui-button-text').text('Switch to Read Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-pencil')              .addClass('ui-silk-book-open');      });            // find all field editors and switch their mode      fieldeditor.mode = 'edit'       fieldeditor.list.render({doNotCache:true});         window.blEditorMode = 'edit';    } else {        // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-book-open').each(function() {          $(this).find('.ui-button-text').text('Switch to Edit Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-book-open')              .addClass('ui-silk-pencil');      });            fieldeditor.mode = 'read'       fieldeditor.list.render({doNotCache:true});             window.blEditorMode = 'read';  }  
    Hope that helps.
  • PeterPeter Member
    edited November 2015
    Thanks! I created this code as a snippet, then created a button in a page title that calls that snippet, but I don't see it doing anything when I click it. Do I need a different kind of button perhaps?
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2013
    That button should do just fine... Ah, I do see one thing I that used to zero in on the particular button I wanted to listen for. If you're not using the "ui-silk-pencil" icon for your button, it won't work. So you'd either need to change the class in the javascript to the particular class that your button has or switch the button to a pencil. This line is what you'd need to change (from "pencil" to whatever icon you're using): $('.ui-button:visible').has('.ui-silk-pencil').each(function() {
  • AndreyVolAndreyVol Member
    edited July 2013
    I tried this but getting the below exception at this line: // find all field editors and switch their mode fieldeditor.mode = 'edit' Uncaught TypeError: Cannot set property 'mode' of undefined Any idea why this may be happening? I checked and see that the below element is present on the page:
    ...
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2018
    Andrey, that likely means your selector for the variable "fieldeditor" didn't return any elements. The line below is what you'll want to check.
    var fieldeditor = $('.nx-basicfieldeditor:visible').data('object');
    A quick way to check would be to open up the js console in Chrome (right click somewhere on the page -> inspect element -> click console) and print the number of items that fit the selector to the log by checking the length:
    console.log($('.nx-basicfieldeditor:visible').length);
    If it returns 0, you need to make sure you have any field editors on the page that meet the selector's criteria (in this case, that they're visible). If that doesn't help, let me know. We can dig deeper.
  • AndreyVolAndreyVol Member
    edited July 2013
    Hi John, Correct, the object fieldeditor is not instantiated at all. And this is where I get stuck. I see the element with this calss on the page, but it is not instantiated/defined via this line: $('.nx-basicfieldeditor:visible') I made a very simple page just to test this out without any extraneous stuff, and it still does not work, getting the same error. The page XML is below. Would you be able to take a look at this? Thanks. var $ = skuid.$; // check for "global" toggle state variable // if it doesn't exist, create it if(!window.blEditorMode) window.blEditorMode = 'read'; // this will grab any visible field editor // you could also do the same with tables by adding // ", .nx-skootable" to the selector var fieldeditor = $('.nx-basicfieldeditor:visible').data('object'); if(window.blEditorMode == 'read') { // toggle button text / icon $('.ui-button:visible').has('.ui-silk-pencil').each(function() { $(this).find('.ui-button-text').text('Switch to Read Mode'); $(this).find('.ui-icon').removeClass('ui-silk-pencil') .addClass('ui-silk-book-open'); }); // find all field editors and switch their mode fieldeditor.mode = 'edit' fieldeditor.list.render({doNotCache:true}); window.blEditorMode = 'edit'; } else { // toggle button text / icon $('.ui-button:visible').has('.ui-silk-book-open').each(function() { $(this).find('.ui-button-text').text('Switch to Edit Mode'); $(this).find('.ui-icon').removeClass('ui-silk-book-open') .addClass('ui-silk-pencil'); }); fieldeditor.mode = 'read' fieldeditor.list.render({doNotCache:true}); window.blEditorMode = 'read'; } {{Name}} {{Model.Label}}
  • AndreyVolAndreyVol Member
    edited December 2019
    the XML got stripped of course in the previous comment. What is the best way to share it with you?
  • Anna WiersemaAnna Wiersema Skuid Mod, Admin 🛠️ 
    edited November 2015
    You should be able to wrap it in
    tags here, or you can email it.
  • AndreyVolAndreyVol Member
    edited July 2013
    Thanks, here is the entire page:
                                        var $ = skuid.$;  // check for "global" toggle state variable  // if it doesn't exist, create it  if(!window.blEditorMode) window.blEditorMode = 'read';  // this will grab any visible field editor  // you could also do the same with tables by adding   // ", .nx-skootable" to the selector  var fieldeditor = $('.nx-basicfieldeditor:visible').data('object');  if(window.blEditorMode == 'read') {      // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-pencil').each(function() {          $(this).find('.ui-button-text').text('Switch to Read Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-pencil')              .addClass('ui-silk-book-open');      });      // find all field editors and switch their mode      fieldeditor.mode = 'edit'       fieldeditor.list.render({doNotCache:true});       window.blEditorMode = 'edit';  } else {      // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-book-open').each(function() {          $(this).find('.ui-button-text').text('Switch to Edit Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-book-open')              .addClass('ui-silk-pencil');      });      fieldeditor.mode = 'read'       fieldeditor.list.render({doNotCache:true});       window.blEditorMode = 'read';  }                                                                                                                                                                                                                                                               {{Name}}                                    {{Model.Label}}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2018
    Ugh, Andrey, this is not what you're going to want to hear... but when I copy and paste your xml into a page and preview it, the mode button works perfectly. One thing to consider is putting the bit of js into the shorthand for $(document).ready(), ie, wrapping it in...
    $(function(){ ... });
    ...to make sure everything's ready to go before the above code is fired.
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2013
    Just make sure the line first line that sets the "var $" value is above it.
  • AndreyVolAndreyVol Member
    edited July 2013
    Hi John, i tried wrapping the code as you suggested and that did not help. However, I added some console output statements and drilled down to the error source. It appears that the fieldeditor element does not have the 'object' entity in its data collection. Please see my updated script with the console statements and the output of the console in the image attached
        var $ = skuid.$;  // check for "global" toggle state variable  // if it doesn't exist, create it    function switchPageMode(){      if(!window.blEditorMode) window.blEditorMode = 'read';      // this will grab any visible field editor      // you could also do the same with tables by adding       // ", .nx-skootable" to the selector            console.log('fieldeditor element: ' + $('.nx-basicfieldeditor:visible'));      console.log('fieldeditor.data: ' +  $('.nx-basicfieldeditor:visible').data);      console.log('fieldeditor.data:  ' +  $('.nx-basicfieldeditor:visible').data('object'));            var fieldeditor = $('.nx-basicfieldeditor:visible').data('object');      if(window.blEditorMode == 'read') {          // toggle button text / icon          $('.ui-button:visible').has('.ui-silk-pencil').each(function() {              $(this).find('.ui-button-text').text('Switch to Read Mode');                          $(this).find('.ui-icon').removeClass('ui-silk-pencil')                  .addClass('ui-silk-book-open');          });          // find all field editors and switch their mode          fieldeditor.mode = 'edit'           fieldeditor.list.render({doNotCache:true});           window.blEditorMode = 'edit';      } else {          // toggle button text / icon          $('.ui-button:visible').has('.ui-silk-book-open').each(function() {              $(this).find('.ui-button-text').text('Switch to Edit Mode');                          $(this).find('.ui-icon').removeClass('ui-silk-book-open')                  .addClass('ui-silk-pencil');          });          fieldeditor.mode = 'read'           fieldeditor.list.render({doNotCache:true});           window.blEditorMode = 'read';      }  }    $(document).ready(function(){      console.log( "ready!" );      switchPageMode();      }    );    
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2018
    Ah, I see. That was a late(r) addition. It probably didn't make it into the version of the package you have. If you go grab a later version (2.43 should do it), that'll fix the problem. Feel free to install the Summer 13 RC, but a lot of folks have been using 2.43 without any trouble. Get the release(s) here: www.skuidify.com/skuidreleases
  • AndreyVolAndreyVol Member
    edited July 2013
    Ok, we are making some progress. I installed 2.43 an d the snippet started to work (bingo!!). Now, when I add it to my page that has multiple field editors, it only toggles the first editor on the page and does not toggle others. Looking at the code, it seems that is supposed to iterate and toggle all field editors it finds on the page. Do I need to add some kind of loop, or change syntax somewhere to get all the editors to toggle?
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2018
    Yep, you're exactly right. See the updated code below. Also, you don't need the $(function() {}); bit either, because it's already wrapped in a snippet that's being called by a button. I must have been thinking about trying to call it on the initial page load. The big change is the addition of the $.each() loops, and swapping the point where you grab the field editors' js info.
    var $ = skuid.$;    // check for "global" toggle state variable  // if it doesn't exist, create it  if(!window.blEditorMode) window.blEditorMode = 'read';    // this will grab any visible field editor  var fieldeditors = $('.nx-basicfieldeditor');    if(window.blEditorMode == 'read') {      // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-pencil').each(function() {          $(this).find('.ui-button-text').text('Switch to Read Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-pencil')              .addClass('ui-silk-book-open');      });            // find all field editors and switch their mode      fieldeditors.each(function(){          // get field editor's js component          var fieldeditor = $(this).data('object');                  fieldeditor.mode = 'edit'           fieldeditor.list.render({doNotCache:true});               });      window.blEditorMode = 'edit';  } else {      // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-book-open').each(function() {          $(this).find('.ui-button-text').text('Switch to Edit Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-book-open')              .addClass('ui-silk-pencil');      });            fieldeditors.each(function(){          // get field editor's js component                  var fieldeditor = $(this).data('object');                  fieldeditor.mode = 'read'           fieldeditor.list.render({doNotCache:true});               });            window.blEditorMode = 'read';  }    
  • AndreyVolAndreyVol Member
    edited July 2013
    Awesome, this worked!! John, thanks for sticking with this. One (hopefully) last bonus round question. This is lower priority, but would be nice to get working as well. I added the selector for tables as well like shown below. It looks that it is finding the table elements and changes the mode, but the table itself does not change on the page. I suspect the problem might be with this line, that renders the component after mode is changed:
    table.list.render({doNotCache:true}); 
    Am I close? Perhaps, table renders differently than field editor?
        var $ = skuid.$;  // check for "global" toggle state variable  // if it doesn't exist, create it  if(!window.blEditorMode) window.blEditorMode = 'read';    // this will grab any visible field editor  var fieldeditors = $('.nx-basicfieldeditor:visible');  //and this wil grab visible tables  var tables = $('.nx-skootable:visible');    if(window.blEditorMode == 'read') {      // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-pencil').each(function() {          $(this).find('.ui-button-text').text('Switch to Read Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-pencil')              .addClass('ui-silk-book-open');      });            // find all field editors and switch their mode      fieldeditors.each(function(){          // get field editor's js component          var fieldeditor = $(this).data('object');            fieldeditor.mode = 'edit'           fieldeditor.list.render({doNotCache:true});               });            // find all tables and switch their mode as well      tables.each(function(){          // get field tables' js component          var table = $(this).data('object');                console.log('table: ' + table);              console.log('table.mode: ' + table.mode);          table.mode = 'edit'               console.log('table.mode: ' + table.mode);          table.list.render({doNotCache:true});               });            window.blEditorMode = 'edit';        } else {      // toggle button text / icon      $('.ui-button:visible').has('.ui-silk-book-open').each(function() {          $(this).find('.ui-button-text').text('Switch to Edit Mode');                      $(this).find('.ui-icon').removeClass('ui-silk-book-open')              .addClass('ui-silk-pencil');      });            fieldeditors.each(function(){          // get field editor's js component                  var fieldeditor = $(this).data('object');            fieldeditor.mode = 'read'           fieldeditor.list.render({doNotCache:true});                       tables.each(function(){          // get field tables' js component          var table = $(this).data('object');                console.log('table: ' + table);              console.log('table.mode: ' + table.mode);          table.mode = 'read'               console.log('table.mode: ' + table.mode);          table.list.render({doNotCache:true});               });                      });      window.blEditorMode = 'read';  }          
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2018
    Right on. Glad that worked. And a couple things: One, you probably want to move the first closing "});" (the end of your fieldeditors loop) up above your tables loop in the else branch. Otherwise... well, you know. Second, with the table, the list apparently has a mode. If you set...
    table.list.mode = 'edit';
    then run the list's render method, it will work.
  • AndreyVolAndreyVol Member
    edited July 2013
    awesome. this worked and looks like I am all set. Thanks for noticing the incorrect closing bracket :) P.S. Might be good to document those components methods as part of your API...
  • John NelsonJohn Nelson Admin, Sonar 🛠️ 
    edited July 2018
    Yep, totally agree. That's one of our priorities for the near future. Exactly how near future remains to be seen at this point, but I'm hopeful. :)
  • Rob HatchRob Hatch Skuad, Sonar ✭✭
    edited September 2016
    Great work John! AndreyVol - I hope your Skuid work is moving forward well. We'd love to see how things are going with your project.
  • AndreyVolAndreyVol Member
    edited August 2013
    Rob, I am definitely taking you up on this offer. Will email you offline.
  • JD BellJD Bell Skuad ✭✭
    edited March 2018
    Nothing like coming late to a party! I've boiled down the code and suggestions from above and made a few changes to make this a bit easier for an inexperienced user to edit:
    // This snippet is designed to be used as a Page Title button. It is, however,
    // easily adaptable for use in other contexts.

    // Rather than hardcoding the button labels, we recommend you use labels to
    // support internationalization.

    // ****************************************
    // Change these variables:
    var 
        // The primary (default) mode
        PrimaryMode         = 'readonly', // Alternatively: 'read'
        PrimaryModeLabel    = 'Edit My Info',
        PrimaryModeIcon     = 'ui-silk-pencil',
        
        // The secondary mode
        SecondaryMode       = 'edit',
        SecondaryModeLabel  = 'All Done',
        SecondaryModeIcon   = 'ui-silk-book',
        
        // Set the selector for which components to toggle. For example, to toggle
        // specific named components, set the selector to:
        //   #ComponentUniqueID1, #AnotherComponent, #AThirdComponent
        // Or, to toggle all field editors and tables, use this selector:
        //   .nx-basicfieldeditor:visible, .nx-skootable
        ComponentSelector   = '.nx-basicfieldeditor:visible, .nx-skootable';
    // ****************************************

    var $ = skuid.$,
        button = arguments[0].button,
        modes = [ PrimaryMode, SecondaryMode ],
        currentEditMode = window.pageEditMode || modes[0],
        newEditMode = ( currentEditMode == modes[0] ) ? modes[1] : modes[0];

    var buttonLabels = {};
    buttonLabels[modes[0]] = PrimaryModeLabel;
    buttonLabels[modes[1]] = SecondaryModeLabel;

    var buttonIcons = {};
    buttonIcons[modes[0]] = PrimaryModeIcon;
    buttonIcons[modes[1]] = SecondaryModeIcon;
        
    // Iterate over the selected components and switch them to the new mode, then
    // force a re-render.
    var componentElems = $( ComponentSelector );
    $.each( componentElems, function( index, componentElem ){
        var component = $( componentElem ).data( 'component' );

        // Currently, this snippet only supports toggling tables and field editors
        // However, it would be relatively easy to add other types of components
        // as appropriate by adding a "case" statement below:
        switch ( component.type ){
            case 'skootable':
            case 'basicfieldeditor':
                var componentObject = $( componentElem ).data( 'object' );
                componentObject.mode = componentObject.list.mode = newEditMode;
                componentObject.list.render({doNotCache:true});
                break;
                
            // case 'othertype':
            //    ...
            //    break;
        }
    });

    // Update the button icon and text to reflect the current mode.
    button.find('.ui-button-text')
        .text( buttonLabels[ newEditMode ] );
    button.find('.ui-icon')
        .removeClass( buttonIcons[ currentEditMode ] )
        .addClass( buttonIcons[ newEditMode ] );
        
    window.pageEditMode = newEditMode;
  • ChrisChris Member
    edited February 2017
    As of the Superbank release, this code no longer works.  The component.type field has been renamed to component._componentType.  The fix is simply to change the switch statement from:
    switch ( component.type ){
    to
    switch ( component._componentType ){
    Enjoy!

    - Chris
  • Greg JarrettGreg Jarrett Member
    edited December 2017
    This toggle edit button is great, and thanks Chris for the recent update for Superbank release.

    I'm wondering if it's possible for this button to also save the model(s) (assuming the user has made changes to the page).

    Right now the user clicks 'All Done' which toggles fields back to 'read' mode, but they still need to hit 'Save' to save the changes. Is it possible for the button to detect if changes have been made on the page, and if so, execute a .save method? 
  • Barry SchnellBarry Schnell Member ✭✭
    edited November 2016
    Chris - We ran in to this as well with the SB release.  We took the approach of using the new "getType" method rather than coding directly to the variable _componentType.  If Skuid decides to change the variable name in the future, it's less likely the getType method would ever change.

    So, instead of:

    component._componentType

    we have:

    component.getType()
  • Barry SchnellBarry Schnell Member ✭✭
    edited December 2017
    Hi Greg -

    Using the action framework with a button marked as "Run multiple actions" you can configure the button to save changes (using one or more models of your choice) and then run snippet which flips the mode back to "read" mode.  Underneath, skuid only "saves" things that have changed so if nothing has changed, nothing will be "saved."

    One thing to note if you go this route is to take advantage of the "On Error" action for the "Save" action so that the screen doesn't flip to read-mode if the "save" fails for some reason.

    Hope this helps!
  • Greg JarrettGreg Jarrett Member
    edited May 2016
    Thanks Barry thats great, I'll give it a go tonight
  • ChrisChris Member
    edited July 2017
    Here is another version of the snippet that we use that has an Edit button, and when you click it, it changes to a Save and a Cancel button (and back again when done).  It also does the appropriate model save/cancel and page update accordingly.  We find this model to be a bit more consistent with how other pages in Skuid operates.  It still doesn't worry about enabling / disabling Save base on if anything has actually changed (which could be added), but it's pretty close to the native Skuid UI design.

    It also changes from using the "window.xxx" context for tracking state to using CSS classes on the buttons - otherwise using this in a Page Include has challenges.

    To use it, place two buttons on the page - one called "Edit" (with a CSS class of btnEdit) and another called "Cancel" (with a CSS classes of btnEditCancel and btnHide).  In the resources, I have a CSS rule for  btnHide that simply does ".btnHide { display: none};" so the Cancel button remains hidden when the page loads.

    EDIT: Updated to use Barry's better approach to getting the type using getType()
    // Set the selector for which components to toggle. For example, to toggle// specific named components, set the selector to:
    //   #ComponentUniqueID1, #AnotherComponent, #AThirdComponent
    // Or, to toggle all field editors and tables, use this selector:
    //   .nx-basicfieldeditor:visible, .nx-skootable
    var ComponentSelector   = '.nx-basicfieldeditor:visible, .nx-skootable';
    var $ = skuid.$;

    // determine what mode was are moving into
    var button = arguments[0].button;
    var startEdit = false;
    var isEditButton = false;
    if (button.hasClass('btnEdit')) {
        startEdit = !button.hasClass('btnEditActive');
        isEditButton = true;
    } else if (button.hasClass('btnEditCancel')) {
        startEdit = false;
    } else {
        console.log('Unknown button in editModeController; missing class btnEdit or btnEditCancel');
        return;
    }
    // Iterate over the selected components and switch them to the new mode, then
    // force a re-render.
    var componentElems = $( ComponentSelector );
    $.each( componentElems, function( index, componentElem ){
        console.log('Processing component');
        console.log(componentElem);
        var component = $( componentElem ).data( 'component' );
        console.log(component);
        
        // Currently, this snippet only supports toggling tables and field editors
        // However, it would be relatively easy to add other types of components
        // as appropriate by adding a "case" statement below:
        switch ( component.getType() ){
            case 'skootable':
            case 'basicfieldeditor':
                var componentObject = $( componentElem ).data( 'object' );
                console.log(componentObject);
                componentObject.mode = componentObject.list.mode = (startEdit ? 'edit' : 'readonly');
                componentObject.list.render({doNotCache:true});
                break;
        }
    });
    // update buttons and model based on what happened
    if (startEdit) {
        // track that we have entered edit mode
        $('.btnEdit').addClass('btnEditActive');
        // unhide cancel
        $('#btnCancelEdit').removeClass('btnHide');
        // adjust edit to correct text and icon
        $('.btnEdit').find('.ui-button-text').text('Save');
        $('.btnEdit').find('.ui-icon').removeClass('sk-icon-page-edit').addClass('sk-icon-save');
    }
    else {
        // track that we have left edit mode
        $('.btnEdit').removeClass('btnEditActive');
        // hide cancel button
        $('#btnCancelEdit').addClass('btnHide');
        // adjust edit to correct text and icon
        $('.btnEdit').find('.ui-button-text').text('Edit');
        $('.btnEdit').find('.ui-icon').removeClass('sk-icon-save').addClass('sk-icon-page-edit');
        // note that normally you would update the model that is associated with this button, with this line:
        // var myModel = arguments[0].model;
        // however, in this case, our model is coming from a page include, so we need more
        // direct reference to the model
        var myModel = skuid.model.getModel('LeadDetails');
        if (isEditButton) {
            myModel.save({callback: function(result){
                if (result.totalsuccess) {
                    myModel.updateData();
                }
            }});
        } else 
            myModel.cancel();
    }
  • ChrisChris Member
    edited January 2015
    Much better!  Thanks - I'll update my code right now.
Sign In or Register to comment.