Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Marc van de Wert 8 posts 75 karma points
    Oct 30, 2015 @ 08:42
    Marc van de Wert
    0

    MultiNodeTreePicker

    Hi all,

    Why can't I use the build in data type: Umbraco.MultiNodeTreePicker as a parameter for a Macro? I've configurated a custom data type that uses the Umbraco.MultiNodeTreePicker and I can use is in a document type, but not in a macro. Why is this and how to solve this / what's the way to go?

  • Carl Jackson 139 posts 478 karma points
    Oct 30, 2015 @ 09:31
    Carl Jackson
    1

    Its because there is no way of adding pre values to a macro parameter.

    You need to make your own version with some hard coded pre values.

    See below for a version we use for a single content picker....

    JS Controller

    angular.module("umbraco")
    .controller("JaywingGridEditors.ParameterEditors.SingleContentPicker",
        function ($scope, assetsService, $http, dialogService, mediaHelper) {
            if ($scope.model.value == null) {
                $scope.model.value = [];
            }
    
            $scope.remove = function (item) {
                $scope.model.value.splice($scope.model.value.indexOf(item), 1);
            };
    
            var options = {
                multiPicker: false,
                section: "content",
                treeAlias: "content",
                filterCssClass: 'not-allowed not-published',
                callback: function (data) {
                    $scope.model.value = {
                        id: data.id,
                        name: data.name
                    }
                }
            }
    
            if ($scope.model.config.StartNodeId) {
                options.startNodeId = $scope.model.config.StartNodeId;
            }
    
            if ($scope.model.config.ContentType) {
                options.filter = $scope.model.config.ContentType;
            }
    
            $scope.pickContent = function (slide) {
                dialogService.treePicker(options);
            };
        }
    );
    

    Angular view

    <div ng-controller="JaywingGridEditors.ParameterEditors.SingleContentPicker">
    <i class="icon icon-add blue"></i>
    <a href ng-click="pickContent(item)">Add/Edit</a>
    
    <ul ng-model="renderModel" ng-if="model.value.name">
        <li>
            <a ng-click="pickContent(item)">
                {{model.value.name}}
            </a>
        </li>
    </ul>
    

    Package manefest

    {
    propertyEditors: [
        {
            alias: "ContentPicker",
            name: "Content picker",
            isParameterEditor: true,
            editor: {
                view: "~/path/to/angular/view.html"
            },
            defaultConfig: {
                ContentType: "ContentType",
                StartNodeId: -1
            }
        }
    ]
    

    }

    You can add multiple versions into the package manifest with different names and pre values for start node and content type and use them as macro parameters!

    Thanks

    Carl

  • Marc van de Wert 8 posts 75 karma points
    Oct 30, 2015 @ 09:33
    Marc van de Wert
    1

    Ok, thank you for the clarification. I was searching the forum and the internet using Google but could not find a satisfying answer.

    Thank you for your answer!

  • Clamond Ng 7 posts 74 karma points
    Feb 17, 2016 @ 06:34
    Clamond Ng
    1

    This is what I gather from different posts about implementing MultiNode Tree Picker as a parameter for a Macro, bits and pieces here and there and finally got it to work.

    Create a custom folder in your App_Plugins, in this case I name it CustomPicker. In that folder create another 3 files, package.manifest, customPicker.controller.js and customPicker.html.

    Copy content from Umbraco source /umbraco/Views/propertyeditors/contentpicker/contentpicker.html and paste it to customPicker.html.

    Then copy just the contentPickerController function in Umbraco source /umbraco/Js/umbraco.controllers.js and paste it to customPickerController.js.

    Make sure to use the same name for your controller and your view. In this case I use "CustomPicker.CustomPickerController";

    Refresh your page, and now you can find MyCustomPickerEditor in your Macro Parameters Type drop down list.

    ~ Enjoy ~

    package.manifest

    {
    "propertyEditors": [{
        "alias": "My.CustomPickerEditor",
        "name": "MyCustomPickerEditor",
        "isParameterEditor": true,
        "editor": {
            "view": "~/App_Plugins/CustomPicker/customPicker.html"
        },
        "defaultConfig": {
            "ContentType": "ContentType",
            "StartNodeId": -1,
            "multiPicker": "1"
        },
        "prevalues": {
            "fields": [{
                "label": "Start node",
                "description": "Choose the default folder",
                "key": "startNodeId",
                "view": "treepicker"
            }]
        }
    }],
    
    "javascript": [
        "/App_Plugins/CustomPicker/customPicker.controller.js"
    ] }
    

    customPicker.html

    <div ng-controller="CustomPicker.CustomPickerController" class="umb-editor umb-contentpicker">
    
    <ng-form name="contentPickerForm">
    
        <ul class="unstyled list-icons"
            ui-sortable
            ng-model="renderModel">
            <li ng-repeat="node in renderModel" ng-attr-title="{{model.config.showPathOnHover && 'Path: ' + node.path || undefined}}">
                <i class="icon icon-navigation handle"></i>
                <a href="#" prevent-default ng-click="remove($index)">
                    <i class="icon icon-delete red hover-show"></i>
                    <i class="{{node.icon}} hover-hide"></i>
                    {{node.name}}
                </a>
    
                <div ng-if="!dialogEditor && ((model.config.showOpenButton && allowOpenButton) || (model.config.showEditButton && allowEditButton))">
                    <small ng-if="model.config.showOpenButton && allowOpenButton"><a href ng-click="showNode($index)"><localize key="open">Open</localize></a></small>
                    <small ng-if="model.config.showEditButton && allowEditButton"><a href umb-launch-mini-editor="node"><localize key="edit">Edit</localize></a></small>
                </div>
            </li>
        </ul>
    
        <ul class="unstyled list-icons" ng-show="model.config.multiPicker === true || renderModel.length === 0">
            <li>
                <i class="icon icon-add blue"></i>
                <a href="#" ng-click="openContentPicker()" prevent-default>
                    <localize key="general_add">Add</localize>
                </a>
            </li>
        </ul>
    
        <!--These are here because we need ng-form fields to validate against-->
        <input type="hidden" name="minCount" ng-model="renderModel" />
        <input type="hidden" name="maxCount" ng-model="renderModel" />
    
        <div class="help-inline" val-msg-for="minCount" val-toggle-msg="minCount">
            You need to add at least {{model.config.minNumber}} items
        </div>
    
        <div class="help-inline" val-msg-for="maxCount" val-toggle-msg="maxCount">
            You can only have {{model.config.maxNumber}} items selected
        </div>
    
    
    </ng-form>
    
    <umb-overlay ng-if="contentPickerOverlay.show"
                 model="contentPickerOverlay"
                 view="contentPickerOverlay.view"
                 position="right">
    </umb-overlay></div>
    

    customPickerController.js

    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    
    function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) {
    
    function trim(str, chr) {
        var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
        return str.replace(rgxtrim, '');
    }
    
    function startWatch() {
        //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
        // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
        // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
        // In their source code there is no event so we need to just subscribe to our model changes here.
        //This also makes it easier to manage models, we update one and the rest will just work.
        $scope.$watch(function () {
            //return the joined Ids as a string to watch
            return _.map($scope.renderModel, function (i) {
                return i.id;
            }).join();
        }, function (newVal) {
            var currIds = _.map($scope.renderModel, function (i) {
                return i.id;
            });
            $scope.model.value = trim(currIds.join(), ",");
    
            //Validate!
            if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
                $scope.contentPickerForm.minCount.$setValidity("minCount", false);
            }
            else {
                $scope.contentPickerForm.minCount.$setValidity("minCount", true);
            }
    
            if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
                $scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
            }
            else {
                $scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
            }
        });
    }
    
    $scope.renderModel = [];
    
    $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
    
    //the default pre-values
    var defaultConfig = {
        multiPicker: false,
        showOpenButton: false,
        showEditButton: false,
        showPathOnHover: false,
        startNode: {
            query: "",
            type: "content",
            id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
        }
    };
    
    if ($scope.model.config) {
        //merge the server config on top of the default config, then set the server config to use the result
        $scope.model.config = angular.extend(defaultConfig, $scope.model.config);
    }
    
    //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
    $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false);
    $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false);
    $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
    $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false);
    
    var entityType = $scope.model.config.startNode.type === "member"
        ? "Member"
        : $scope.model.config.startNode.type === "media"
        ? "Media"
        : "Document";
    $scope.allowOpenButton = entityType === "Document" || entityType === "Media";
    $scope.allowEditButton = entityType === "Document";
    
    //the dialog options for the picker
    var dialogOptions = {
        multiPicker: $scope.model.config.multiPicker,
        entityType: entityType,
        filterCssClass: "not-allowed not-published",
        startNodeId: null,
        callback: function (data) {
            if (angular.isArray(data)) {
                _.each(data, function (item, i) {
                    $scope.add(item);
                });
            } else {
                $scope.clear();
                $scope.add(data);
            }
            angularHelper.getCurrentForm($scope).$setDirty();
        },
        treeAlias: $scope.model.config.startNode.type,
        section: $scope.model.config.startNode.type
    };
    
    //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the 
    // pre-value config on to the dialog options
    angular.extend(dialogOptions, $scope.model.config);
    
    //We need to manually handle the filter for members here since the tree displayed is different and only contains
    // searchable list views
    if (entityType === "Member") {
        //first change the not allowed filter css class
        dialogOptions.filterCssClass = "not-allowed";
        var currFilter = dialogOptions.filter;
        //now change the filter to be a method
        dialogOptions.filter = function (i) {
            //filter out the list view nodes
            if (i.metaData.isContainer) {
                return true;
            }
            if (!currFilter) {
                return false;
            }
            //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, 
            // but not much we can do about that since members require special filtering.
            var filterItem = currFilter.toLowerCase().split(',');
            var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
            if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) {
                return true;
            }
    
            return false;
        }
    }
    
    
    //if we have a query for the startnode, we will use that. 
    if ($scope.model.config.startNode.query) {
        var rootId = $routeParams.id;
        entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
            dialogOptions.startNodeId = ent.id;
        });
    } else {
        dialogOptions.startNodeId = $scope.model.config.startNode.id;
    }
    
    //dialog
    $scope.openContentPicker = function () {
        $scope.contentPickerOverlay = dialogOptions;
        $scope.contentPickerOverlay.view = "treepicker";
        $scope.contentPickerOverlay.show = true;
    
        $scope.contentPickerOverlay.submit = function (model) {
    
            if (angular.isArray(model.selection)) {
                _.each(model.selection, function (item, i) {
                    $scope.add(item);
                });
            }
    
            $scope.contentPickerOverlay.show = false;
            $scope.contentPickerOverlay = null;
        }
    
        $scope.contentPickerOverlay.close = function (oldModel) {
            $scope.contentPickerOverlay.show = false;
            $scope.contentPickerOverlay = null;
        }
    
    };
    
    $scope.remove = function (index) {
        $scope.renderModel.splice(index, 1);
        angularHelper.getCurrentForm($scope).$setDirty();
    };
    
    $scope.showNode = function (index) {
        var item = $scope.renderModel[index];
        var id = item.id;
        var section = $scope.model.config.startNode.type.toLowerCase();
    
        entityResource.getPath(id, entityType).then(function (path) {
            navigationService.changeSection(section);
            navigationService.showTree(section, {
                tree: section, path: path, forceReload: false, activate: true
            });
            var routePath = section + "/" + section + "/edit/" + id.toString();
            $location.path(routePath).search("");
        });
    }
    
    $scope.add = function (item) {
        var currIds = _.map($scope.renderModel, function (i) {
            return i.id;
        });
    
        if (currIds.indexOf(item.id) < 0) {
            item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path });
        }
    };
    
    $scope.clear = function () {
        $scope.renderModel = [];
    };
    
    var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
        var currIds = _.map($scope.renderModel, function (i) {
            return i.id;
        });
        $scope.model.value = trim(currIds.join(), ",");
    });
    
    //when the scope is destroyed we need to unsubscribe
    $scope.$on('$destroy', function () {
        unsubscribe();
    });
    
    //load current data
    var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
    entityResource.getByIds(modelIds, entityType).then(function (data) {
    
        //Ensure we populate the render model in the same order that the ids were stored!
        _.each(modelIds, function (id, i) {
            var entity = _.find(data, function (d) {
                return d.id == id;
            });
    
            if (entity) {
                entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
                $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path });
            }
    
    
        });
    
        //everything is loaded, start the watch on the model
        startWatch();
    
    });
    }angular.module('umbraco').controller("CustomPicker.CustomPickerController", contentPickerController);
    
  • Jason Espin 368 posts 1335 karma points
    Apr 20, 2016 @ 16:06
    Jason Espin
    0

    Good work overall but unfortunately, I cannot get this to work. I add it as a new datatype and try to define the root node but all it does is take me to the homepage.

    I then tried adding it directly as a macro parameter and it will not let me select anything at all.

    I still can't believe that something like this is not in Umbraco as standard.

  • Max 80 posts 437 karma points
    Aug 29, 2017 @ 14:32
    Max
    0

    I realise this is a bump... Until I updated to 7.6.5 .... I had this working (a multinode treepicker as a parameter in my macro)... When I ran this previously

    Model.MacroParamters["mymultinodetreepicker"]... gave me a CSV list of node IDs... then I used Umbraco.TypedContent(id) to get the IPublishedContent Item....

        @inherits Umbraco.Web.Macros.PartialViewMacroPage
    @{
        var items = Model.MacroParameters["SelectedHighlightSections"].ToString().Trim();
    
        string[] _items = new string[0] { };
    
        _items = (items.Length > 0) ? items.Split(',') : _items;
        }
    @if (_items.Length > 0)
        {
        <section id="highlight-section" class="sections highlight-section">
            <div class="container-fluid">
                <div class="row text-center">
                    @for (var i = 0; i < _items.Length; i++)
                    {
                        var item = Umbraco.TypedContent(_items[i]);
    ..... more stuff
    

    Now my Model.MacroParameters call returns an object string... not a List

    This is a documented breaking change with 7.6.(2-3)

Please Sign in or register to post replies

Write your reply to:

Draft