I have a button that links out to an external URL by using an action that triggers a result-view with an app-launch.
Button:
input-cell {
label ("#{value(button.text)}")
on-click {
intent {
goal: FetchUrl
value-set: UrlConcept { $expr(button.value) }
}
}
}
Action:
action (FetchUrl) {
description (Fetches External Url)
type (Constructor)
collect {
input (url) {
type (UrlConcept)
min (Required) max (One)
}
}
output (UrlConcept) {
evaluate {
UrlConcept$expr (url)
}
}
}
Model:
text (UrlConcept) {
description (External Url)
}
Result View:
result-view {
match: UrlConcept (this) {
min(Required) max (One)
}
render {
nothing
}
app-launch {
payload-uri {
template ("#{value(this)}")
}
}
}
This works as expected in the Bixby Simulator - clicking the button results in the message 'User successfully exits Bixby, redirecting to an app', and I can view the logs and see the correct URI.
However, this fails when tested on multiple physical devices. Clicking the button results in Bixby hanging forever.
I am looking to pass/store user's speech input. Bixby gives the user a result-view list of items, of which the user will say what item they want. Bixby will then display a list of account names, of which the user will say what account. I want to store what the user says after each list to combine them for an API call later.
Currently, I have only created the lists Bixby displays after each user input, but I do not know how to go forward to use both of the user inputs to use in a API call. My authorization.bxb is all configured and works, it is strictly only being able to forward information from multiple "moments." I have tried creating a input-view using selection-of, but continued to have issues displaying a list.
PossibleDataMetrics.view.bxb
result-view {
match: Metric (metric) {
from-output: ListMetrics
}
message {
template ("What data metrics are you looking for?")
}
render {
layout {
section {
content {
partitioned {
content {
for-each (metric){
as (m) {
title-area {
slot1 {
text {
value ("#{value(m.metrics)}")
style (Title_S)
}
}
}
}
}
}
}
}
}
}
}
}
ProfileTitleCardResultView.view.bxb
result-view {
match: Profile (profile) {
from-output: GetProfiles
}
message {
template ("What profile would you like?")
}
render {
layout {
section {
content {
for-each (profile){
as (view) {
title-card {
title-area {
halign (Start)
slot1 {
single-line {
text {
style (Detail_L_Soft)
value ("Account: #{value(view.acctName)}")
}
}
}
slot2 {
single-line {
text {
style (Detail_M_Soft)
value ("Web property: #{value(view.webName)}")
}
}
}
slot3 {
single-line {
text {
style (Title_S)
value ("Profile: #{value(view.viewName)}")
}
}
}
}
}
}
}
}
}
}
}
}
When these result views are called, I am looking to take the user input from both of these list of results to use in another action to create another list that is based off the user's answer of the initial 2 results.
In general, To navigate away from result-view you have the following options
Use followup to pose a question to the user and use the answer to
navigate away from the result-view https://bixbydevelopers.com/dev/docs/reference/type/result-view.followup
Use on-click feature of cards https://bixbydevelopers.com/dev/docs/reference/type/layout-macro-def.content.map-card.on-click
The other way to accomplish the same is by calling an Action that collects these inputs which invokes the input-view for each of these.
Hope this helps!
I'm using Orchard 1.9.3 and followed some tutorials on how to create a custom normal Element in Orchard. I couldn't find any specifically on creating container elements so I dug around a bit in the source and this is what I have so far:
Elements/Procedure.cs
public class Procedure : Container
{
public override string Category
{
get { return "Content"; }
}
public override string ToolboxIcon
{
get { return "\uf0cb"; }
}
public override LocalizedString Description
{
get { return T("A collection of steps."); }
}
public override bool HasEditor
{
get { return false; }
}
}
Drivers/ProcedureElementDriver.cs
public class ProcedureElementDriver : ElementDriver<Procedure> {}
Services/ProcedureModelMap
public class ProcedureModelMap : LayoutModelMapBase<Procedure> {}
Views/LayoutEditor.Template.Procedure
#using Orchard.Layouts.ViewModels;
<div class="layout-element-wrapper" ng-class="{'layout-container-empty': getShowChildrenPlaceholder()}">
<ul class="layout-panel layout-panel-main">
<li class="layout-panel-item layout-panel-label">Procedure</li>
#Display()
#Display(New.LayoutEditor_Template_Properties(ElementTypeName: "procedure"))
<li class="layout-panel-item layout-panel-action" title="#T("Delete {{element.contentTypeLabel.toLowerCase()}} (Del)")" ng-click="delete(element)"><i class="fa fa-remove"></i></li>
<li class="layout-panel-item layout-panel-action" title="#T("Move {{element.contentTypeLabel.toLowerCase()}} up (Ctrl+Up)")" ng-click="element.moveUp()" ng-class="{disabled: !element.canMoveUp()}"><i class="fa fa-chevron-up"></i></li>
<li class="layout-panel-item layout-panel-action" title="#T("Move {{element.contentTypeLabel.toLowerCase()}} down (Ctrl+Down)")" ng-click="element.moveDown()" ng-class="{disabled: !element.canMoveDown()}"><i class="fa fa-chevron-down"></i></li>
</ul>
<div class="layout-container-children-placeholder">
#T("Drag a steps here.")
</div>
#Display(New.LayoutEditor_Template_Children())
All of this is more or less copied from the Row element. I now have a Procedure element that I can drag from the Toolbox onto my Layout but it is not being rendered with my template, even though I can override the templates for the other layout elements this way, and I still can't drag any children into it. I had hoped that simply inheriting from Container would have made that possible.
I essentially just want to make a more restrictive Row and Column pair to apply some custom styling to a list of arbitrary content. How can I tell Orchard that a Procedure can only be contained in a Column and that it should accept Steps (or some other element) as children?
I figured out how to make container and containable elements from looking at Mainbit's layout module.
The container elements require some additional Angular code to make them work. I still need help figuring out how to limit which elements can be contained!
Scripts/LayoutEditor.js
I had to extend the LayoutEditor module with a directive to hold all of the Angular stuff pertaining to my element:
angular
.module("LayoutEditor")
.directive("orcLayoutProcedure", ["$compile", "scopeConfigurator", "environment",
function ($compile, scopeConfigurator, environment) {
return {
restrict: "E",
scope: { element: "=" },
controller: ["$scope", "$element",
function ($scope, $element) {
scopeConfigurator.configureForElement($scope, $element);
scopeConfigurator.configureForContainer($scope, $element);
$scope.sortableOptions["axis"] = "y";
}
],
templateUrl: environment.templateUrl("Procedure"),
replace: true
};
}
]);
Scripts/Models.js
And a Provider for Orchard's LayoutEditor to use:
var LayoutEditor;
(function (LayoutEditor) {
LayoutEditor.Procedure = function (data, htmlId, htmlClass, htmlStyle, isTemplated, children) {
LayoutEditor.Element.call(this, "Procedure", data, htmlId, htmlClass, htmlStyle, isTemplated);
LayoutEditor.Container.call(this, ["Grid", "Content"], children);
//this.isContainable = true;
this.dropTargetClass = "layout-common-holder";
this.toObject = function () {
var result = this.elementToObject();
result.children = this.childrenToObject();
return result;
};
};
LayoutEditor.Procedure.from = function (value) {
var result = new LayoutEditor.Procedure(
value.data,
value.htmlId,
value.htmlClass,
value.htmlStyle,
value.isTemplated,
LayoutEditor.childrenFrom(value.children));
result.toolboxIcon = value.toolboxIcon;
result.toolboxLabel = value.toolboxLabel;
result.toolboxDescription = value.toolboxDescription;
return result;
};
LayoutEditor.registerFactory("Procedure", function (value) {
return LayoutEditor.Procedure.from(value);
});
})(LayoutEditor || (LayoutEditor = {}));
This specifically is the line that tells the element what it can contain:
LayoutEditor.Container.call(this, ["Grid", "Content"], children);
ResourceManifest.cs
Then I made a resource manifest to easily make these available in Orchard's module.
public class ResourceManifest : IResourceManifestProvider
{
public void BuildManifests(ResourceManifestBuilder builder)
{
var manifest = builder.Add();
manifest.DefineScript("MyModule.Models").SetUrl("Models.js").SetDependencies("Layouts.LayoutEditor");
manifest.DefineScript("MyModule.LayoutEditors").SetUrl("LayoutEditor.js").SetDependencies("Layouts.LayoutEditor", "MyModule.Models");
}
}
By default, .SetUrl() points to the /Scripts folder in your module/theme.
Handlers/LayoutEditorShapeEventHandler.cs
Finally, I added this handler to load my scripts on the admin pages that use the Layout Editor.
public class LayoutEditorShapeEventHandler : IShapeTableProvider
{
private readonly Work<IResourceManager> _resourceManager;
public LayoutEditorShapeEventHandler(Work<IResourceManager> resourceManager)
{
_resourceManager = resourceManager;
}
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("EditorTemplate").OnDisplaying(context =>
{
if (context.Shape.TemplateName != "Parts.Layout")
return;
_resourceManager.Value.Require("script", "MyModule.LayoutEditors");
});
}
}
Hopefully, this will help someone out in the future. However, I still don't know how to make it so that my Container will only contain my Containable or that my Containable will only allow itself to be contained by my Container. It seems like adjusting LayoutEditor.Container.call(this, ["Grid", "Content"], children); would have been enough to achieve this, but it's not. More help is still welcome.
First of all, thank you for this answer. I found it really helpful. Still, I ended up having problems to restrict where my container element could be placed and what could be placed inside of it.
I've noticed that those restrictions are made based on the category of the element. Canvas, Grid, Row, Column or Content.
Orchard goes through all categories and runs some code to understand where the items inside that category can be placed.
Anything outside Orchard's Layout Category is a Content. If you have a lot of different custom categories for a variety of custom elements, they are still Contents in Orchard's eyes. So... For every category you have, Orchard's gonna run some code and say that every item inside that category is actually a Content and they all end up having the same placement rules.
I didn't want any of my custom container to be placeable inside another custom container, and I didn't want anything other then content being placed inside my custom containers, so I ended up doing the following steps:
Go to your Procedure.cs file and change your class' category.
public override string Category => "Container";
Go to your Models.js file and change the value in the "dropTargetClass" property.
this.dropTargetClass = 'layout-common-holder layout-customcontainer';
Go to the LayoutEditor.Template.ToolboxGroup.cshtml file (you could create your own in your theme) and change the value in the "ui-sortable" attribute in the ul element.
ui-sortable="category.name == 'Container' ?
$parent.getSortableOptions(category.name) : $parent.getSortableOptions('Content')"
Go to the Toolbox.js file and edit the "getSortableOptions" function, so it contains a case for your newly created "Container" category. Pay attention to where the "layout-customcontainer" class appears bellow. I wanted to remove the ability of placing grids, and other layout elements inside my container, so I had to change their cases too.
switch (type) {
case "Container":
parentClasses = [".layout-column", ".layout-common-holder:not(.layout-customcontainer)"];
placeholderClasses = "layout-element layout-container ui-sortable-placeholder";
break;
case "Grid":
parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder:not(.layout-customcontainer)"];
placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder";
break;
case "Row":
parentClasses = [".layout-grid"];
placeholderClasses = "layout-element layout-container layout-row row ui-sortable-placeholder";
break;
case "Column":
parentClasses = [".layout-row:not(.layout-row-full)"];
placeholderClasses = "layout-element layout-container layout-column ui-sortable-placeholder";
floating = true; // To ensure a smooth horizontal-list reordering. https://github.com/angular-ui/ui-sortable#floating
break;
case "Content":
parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"];
placeholderClasses = "layout-element layout-content ui-sortable-placeholder";
break;
case "Canvas":
parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder:not(.layout-container)"];
placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder";
break;}
Run the Gulpfile.js task, so your changes are placed inside Orchard's LayoutEditor.js file.
Now, you have a container element with some custom restrictions.
I hope it's not to late to be useful to you.
I have a View with 2 textbox and a button that call an action on a ViewModel to show another View; that what will show in it depends on values of 2 texbox.
For that reason before to call my ViewModel i want to check textbox values and if its are empty show a Dialog. Now to call my ViewModel i have add a binding like this:
this.AddBindings(new Dictionary<object, string>()
{
{ btnSearch, "TouchUpInside GoParameterizedCommand" },
});
as Swiss Binding. Now if i want to use same event to check if my textbox are valorized and don't call GoParameterizedCommand, how could i do?
You could bind all your controls to ViewModel properties like:
this.AddBindings(new Dictionary<object, string>()
{
{ btnSearch, "TouchUpInside GoCommand" },
{ text1, "Text MyText" },
{ switch1, "On MyOption" },
// ...
};
Then inside the GoCommand handler you could put whatever logic you need:
public ICommand GoCommand
{
get
{
return new MvxCommand(() => {
if (MyOption)
{
ShowViewModel<OptionViewModel>();
}
else
{
ShowViewModel<DetailViewModel>(new { text = MyText });
}
});
}
}
For showing a dialog - eg an error dialog - then this might be best done using a messenger - sending an error message from the viewmodel. There are a few questions on here about error handling - eg http://slodge.blogspot.co.uk/2012/05/one-pattern-for-error-handling-in.html - plenty of other options are available for giving the user a hint about what to do - eg you could bind the background colour of the text field to an IsMyTextValid property.
I have a custom list, created a view for it. One view for normal users another for admins. When you go to add something new to the list the fields I want to hide from normal users are there. Is there a setting somewhere to fix or change this?
You can add this to the controller behind the view:
public ActionResult Index()
{
ViewBag.isAdmin = isAdmin();
return View();
}
private Boolean isAdmin()
{
if (User.IsInRole("admin"))
{
return true;
}
else
{
return false;
}
}
and then in the View, wrap the context you need with:
#{
if (ViewBag.isAdmin)
{
//....only admins can see me!
}
}