1

I am using examples from great article from here https://www.codeproject.com/Articles/888923/SharePoint-Client-Side-Rendering-List-Forms-p

and use example to add new Lookup value directly from form. It works, but don't understand how to implement this for few fields, for example if I have "SubType1" and "SubType2", and want same logic for "SubType2" field too

here is my example code: https://jsfiddle.net/Rockietm/rjfk539f/

If You have lookup field "FieldSubType" than You see small "Add" link and input field if You press it:

enter image description here

But I am newbie with KnockoutJs, so how to add same functionality for two fields: for FieldSubType and for example SecondFieldSubType?

Update:

Thank You!

So I put templates in one variable and can use it like

var keys = Object.keys(myFields);
console.log('myFields object contains ' + keys.length + ' keys: ' + keys);

But how to bind two models on same page? I tried to wrap-up in two with 'with', like described here: http://www.mytecbits.com/internet/knockout-js-binding-multiple-view-models

var html = "<div data-bind='with: Geography'>";
// ..
html += '</div>';

But no result, what I see is:

enter image description here

I have two fields: 'Geography' and 'Sub Type'. Here is my example code (it runs multiple times for every field but is is example):

 "use strict";

    if (!window.console) window.console = {};
    if (!window.console.log) window.console.log = function () { };

    var defaultLookupFieldTemplate = SPClientTemplates._defaultTemplates.Fields.default.all.all.Lookup.NewForm;

    var myFields = {
        'Geography': {
            NewForm: function (ctx) {
                var html = "<div data-bind='with: Geography'>";
                html += '<div id="myGeographyLookup" data-bind="visible: !editMode()">' + defaultLookupFieldTemplate(ctx);
                html += '<a href="javascript:void()" data-bind="click: addNewItem" title="Add new Geography item">Add</span>';
                html += '</div>';
                html += '<div data-bind="visible: editMode()">';
                html += '<input type="text" data-bind="value: newTitle" />';
                html += '<input type="button" data-bind="click: saveGeographyChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
                html += '</div>';
                html += "</div>";

                return html;
            },
            EditForm: function (ctx) {
                var html = "<div data-bind='with: Geography'>";
                html += '<div id="myGeographyLookup" data-bind="visible: !editMode()">' + defaultLookupFieldTemplate(ctx);
                html += '<a href="javascript:void()" data-bind="click: addNewItem" title="Add new Geography item">Add</span>';
                html += '</div>';
                html += '<div data-bind="visible: editMode()">';
                html += '<input type="text" data-bind="value: newTitle" />';
                html += '<input type="button" data-bind="click: saveGeographyChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
                html += '</div>';
                html += "</div>";
                return html;
            }
        },
        'Sub_x002d_type': {
            NewForm: function (ctx) {
                var html = "<div data-bind='with: Sub_x002d_type'>";
                html += '<div id="mySubtypeLookup" data-bind="visible: !editSubtypeMode()">' + defaultLookupFieldTemplate(ctx);
                html += '<a href="javascript:void()" data-bind="click: addNewSubtype" title="Add new Sub-type item">Add</span>';
                html += '</div>';
                html += '<div data-bind="visible: editSubtypeMode()">';
                html += '<input type="text" data-bind="value: newSubtypeTitle" />';
                html += '<input type="button" data-bind="click: saveSubtypeChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
                html += '</div>';
                html += '</div>';
                return html;
            },
            EditForm: function (ctx) {
                var html = "<div data-bind='with: Sub_x002d_type'>";
                html += '<div id="mySubtypeLookup" data-bind="visible: !editSubtypeMode()">' + defaultLookupFieldTemplate(ctx);
                html += '<a href="javascript:void()" data-bind="click: addNewSubtype" title="Add new Sub-type item">Add</span>';
                html += '</div>';
                html += '<div data-bind="visible: editSubtypeMode()">';
                html += '<input type="text" data-bind="value: newSubtypeTitle" />';
                html += '<input type="button" data-bind="click: saveSubtypeChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
                html += '</div>';
                html += '</div>';
                return html;
            }
        }
    }

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides({

        OnPostRender: function (ctx) {

            var pageModel = {
                'Geography': {
                    editMode: ko.observable(false),
                    newTitle: ko.observable(''),
                    addNewItem: function () {
                        this.editMode(true);
                    },

                    saveGeographyChanges: function () {
                        // .. some logic
                        console.log("Added 'Geography' lookup");
                        this.editMode(false);
                    }
                },
                'Sub_x002d_type': {
                    editSubtypeMode: ko.observable(false),
                    newSubtypeTitle: ko.observable(''),
                    addNewSubtype: function () {
                        this.editSubtypeMode(true);
                    },
                    saveSubtypeChanges: function () {
                        // some logic here
                        console.log("Added Sub-Type lookup");
                        this.editSubtypeMode(false);
                    }
                }
            };

            // How to do it?..
            ko.applyBindings(pageModel);
        },
        Templates: {
            Fields: myFields
        }
    });

Also note your CSR is not MDS proof

Thank You, but Publishing Features are enabled on this site, so MDS does not fire here

Update2:

Also tried to bind by ID, but no result:

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
OnPostRender: function (ctx) {

    var pageModel1 = {
            editMode: ko.observable(false),
            newTitle: ko.observable(''),
            addNewItem: function () {
                this.editMode(true);
            },

            saveGeographyChanges: function () {
                console.log("Added 'Geography' lookup");
                this.editMode(false);
            }
    };

    var pageModel2 = {

            editSubtypeMode: ko.observable(false),
            newSubtypeTitle: ko.observable(''),
            addNewSubtype: function () {
                this.editSubtypeMode(true);
            },
            saveSubtypeChanges: function () {
                console.log("Add Sub-Tupe lookup");
                this.editSubtypeMode(false);
            }
    };

    ko.applyBindings(pageModel1, document.getElementById("myGeographyLookup"));
    ko.applyBindings(pageModel2, document.getElementById("mySubtypeLookup"));
},
Templates: {
    Fields: myFields
}

});

Update3:

I also tried to apply bindings outside of CSR:

_spBodyOnLoadFunctionNames.push("registerModel");

it works for first field('Geography'), but get "Uncaught TypeError: this.editSubtypeMode is not a function" editing 'Sub-type' field(

Don't understant how to apply bindings for two fields(

Here is jsfiddle: https://jsfiddle.net/Rockietm/bhrp79yu/

"use strict";

var defaultLookupFieldTemplate = SPClientTemplates._defaultTemplates.Fields.default.all.all.Lookup.NewForm;

var myFields = {
    'Geography': {
        NewForm: function(ctx) {

            var html = '<div id="myGeographyLookup" data-bind="visible: !editMode()">' + defaultLookupFieldTemplate(ctx);
            html += '<a href="javascript:void()" data-bind="click: addNewItem" title="Add new Geography item">Add</span>';
            html += '</div>';
            html += '<div data-bind="visible: editMode()">';
            html += '<input type="text" data-bind="value: newTitle" />';
            html += '<input type="button" data-bind="click: saveGeographyChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
            html += '</div>';

            return html;
        },
        EditForm: function(ctx) {

            var html = '<div id="myGeographyLookup" data-bind="visible: !editMode()">' + defaultLookupFieldTemplate(ctx);
            html += '<a href="javascript:void()" data-bind="click: addNewItem" title="Add new Geography item">Add</span>';
            html += '</div>';
            html += '<div data-bind="visible: editMode()">';
            html += '<input type="text" data-bind="value: newTitle" />';
            html += '<input type="button" data-bind="click: saveGeographyChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
            html += '</div>';

            return html;
        }
    },

    'Sub_x002d_type': {
        NewForm: function (ctx) {
            var html = '<div id="mySubtypeLookup" data-bind="visible: !editSubtypeMode()">' + defaultLookupFieldTemplate(ctx);
            html += '<a href="javascript:void()" data-bind="click: addNewSubtype" title="Add new Sub-type item">Add</span>';
            html += '</div>';
            html += '<div data-bind="visible: editSubtypeMode()">';
            html += '<input type="text" data-bind="value: newSubtypeTitle" />';
            html += '<input type="button" data-bind="click: saveSubtypeChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
            html += '</div>';

            return html;
        },
        EditForm: function (ctx) {
            var html = '<div id="mySubtypeLookup" data-bind="visible: !editSubtypeMode()">' + defaultLookupFieldTemplate(ctx);
            html += '<a href="javascript:void()" data-bind="click: addNewSubtype" title="Add new Sub-type item">Add</span>';
            html += '</div>';
            html += '<div data-bind="visible: editSubtypeMode()">';
            html += '<input type="text" data-bind="value: newSubtypeTitle" />';
            html += '<input type="button" data-bind="click: saveSubtypeChanges, clickBubble: false" value="Save" title="Click and select new value from dropdown" />';
            html += '</div>';
            return html;
        }
    }
};

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({

    Templates: {
        Fields: myFields
    }
});

function registerModel() {

    var pageModel = {
        editMode: ko.observable(false),
        newTitle: ko.observable(''),
        addNewItem: function () {
            this.editMode(true);
        },

        saveGeographyChanges: function () {
            console.log("Added 'Geography' lookup");
            this.editMode(false);
        },

        editSubtypeMode: ko.observable(false),
        newSubtypeTitle: ko.observable(''),
        addNewSubtype: function () {
            this.editSubtypeMode(true);
        },

        saveSubtypeChanges: function () {
                console.log("Added Subtype lookup value");
                this.editSubtypeMode(false);
        }
    };

    ko.applyBindings(pageModel);
}

_spBodyOnLoadFunctionNames.push("registerModel");

Resolved:

Ok, thank You for answer! Finally defined knockout outside of csr. Ugly solution, but now it works and I added code for several fields: https://jsfiddle.net/Rockietm/un4uvoh9/

function registerModel() {

    var pageModel = {
        editGeography: ko.observable(false),
        newGeography: ko.observable(''),
        addGeography: function() {
            this.editGeography(true);
        },

        saveGeographyChanges: function() {
            console.log("Added 'Geography' lookup");

            // some code to add new lookup item and populate and append child to dropdown..
            pageModel.editGeography(false);
        },

        editSubType: ko.observable(false),
        newSubType: ko.observable(''),
        addSubtype: function() {
            this.editSubType(true);
        },

        saveSubtypeChanges: function() {
            // ..
            pageModel.editSubType(false);
        }
    };

    ko.applyBindings(pageModel);
}

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({

    Templates: {
        Fields: myFields
    }
});

_spBodyOnLoadFunctionNames.push("registerModel");

1 Answer 1

2

You have to create the whole { } object you provide to CSR dynamically
most likely you will stuff it in one myFields Object with a definition for every field

Then instead of:

Templates: {
        Fields: {
            'FieldSubType':

you use it as:

Templates: {
        Fields: myFields

And in your OnPostRender function proces that whole Array again to dynamically configure all KnockOut

(ES5) JavaScripts' Object.keys() is your friend in all this


Also note your CSR is not MDS proof, use the Cisar Chrome Extension to WYSIWYG write CSR code, it will give you the complete CSR boilerplate that is MDS save

-- Gennady G: deleted

2
  • Hi Danny! Thank You for answer. I moved fields inside variable but don't understand how to bind two models inside OnPostRender
    – Gennady G
    May 10, 2017 at 19:33
  • 1
    You have to get the DOM elements dynamically and then apply KO dynamically... yes it is some advanced JS required.. its like driving one of my motorcycles... I can't teach you that by email... First time I did programming patterns like these it took me months May 11, 2017 at 8:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.