Using GridView as a template object - How to set ListModel as an alias? - string

I'm currently working on a large QML project, and I've come to a point where it would be advantageous to create what is essentially a template of a page, to which I can add as little information as possible in the definition of each page.
Currently, I have a MyPage.qml file, which defines objects such as the background mousearea and the elements which each page have in common such as a back button, but then I come to my problem - where not every page is alike.
The elements in each page are aligned in a grid. Initially I had used rows and columns to organize those elements, but I've come under the impression that a GridView object would be more advantageous.
As it stands, here is my implementation:
Item {
//MyPage.qml
id: page
default property alias contentmodel: content.model
...
GridView{
id: content
anchors.fill: parent
cellWidth: parent.width/4
cellHeight: parent.height/6
focus: true
model: contentmodel
delegate: Item{
height: content_background.height/6
width: content_background.width/4
property var entry: entries.split(",")
MyComboBox{
id: del_combo
height: parent.height
width: parent.width
entries: entry
}
}
}
}
This is then instantiated from an actual page definition whose important features are:
MyPage {
...
ListModel {
id: contentmodel
ListElement { entries: "string1,string2" }
ListElement { entries: "string3,string4,string5,string6,string7" }
...
}
}
In this manner, I aim to have different pages consisting of different combo boxes, pulling their similar code from the same source, and with minimal overlap.
Also worthy of note is the nature of "entries". It is a string consisting of concatenated substrings, separated by commas, which is then split back into its component parts within MyPage.qml. This is the same as recommended from QML ListElement pass list of strings
MyPage.qml is having issues receiving this string, often only providing the first element to a MyComboBox object or none at all.
So my question to you is, where did I go wrong in this implementation? Is there something fundamentally wrong with my approach? Are there alternatives to using aliasing? Do you have any example code which is able to accomplish this?
Thank you for your time.

You have defined a property contentmodel in MyPage.qml.
When using MyPagecomponent you need to attribute your ListModel to the contentmodel property, not set your ListModel id to contentmodel.
This should work :
MyPage {
contentmodel : ListModel {
ListElement { entries: "string1,string2" }
ListElement { entries: "string3,string4,string5,string6,string7" }
}
}

Related

How to get position of clicked icon on cesium map

I am trying to draw circles around an icon that is selected via clicking. My current code is:
this.handler.setInputAction(function(click) {
var pickedObjects = viewer.scene.drillPick(click.Position);
if(Cesium.defined(pickedObjects)) {
if(pickedObjects.length >=1)
{
var cartesian = thisRef.viewer.camera.pickEllipsoid(click.position, thisRef.viewer.scene.globe.ellipsoid);
thisRef.drawCircle(cartesian);
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK;
};
If the user is zoomed out quite far, the position won't be accurate. It needs to be based on the selected object, not the users click. However I can't figure out how to do this. I have pickedObjects, but I can't figure out how to get their position from those objects. It doesn't seem to be an entity (even though I think the icon was an entity when it was being created) and so I can't use entity.position. Thank you for your help.
To be able to access the standard Cesium entity, it turns out, you must go in the drillPick objects id. So I modified my code to get the first object in the list of objects and get the id from that, and now I can call the member position of a standard entity.
this.handler.setInputAction(function(click) {
var pickedObjects = viewer.scene.drillPick(click.Position);
if(Cesium.defined(pickedObjects)) {
if(pickedObjects.length >=1)
{
var entity = pickedObjects[0].id;
thisRef.drawCircle(entity.position);
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK;
};

Why does using FetchedResults work for Text and not TextField? SwiftUI, CoreData/ FetchRequest

I am getting CoreData properties from a FetchRequest and want to use it to pre-populate a text field (a user's Email).
Here is the FetchRequest
#FetchRequest(
entity: Account.entity(),
sortDescriptors:[
NSSortDescriptor(keyPath: \Account.id, ascending: true)
]
)var accounts: FetchedResults<Account>
Note I have all of the proper persistant container and #environment stuff set up to use #FetchRequest like this.
Then in my View stack I have:
var body: some View{
ZStack {
Form {
Section(header: Text("EMAIL")) {
ForEach(accounts, id: \.self) {account in
TextField("Email", text:account.email) // This is where I get an error (see error below)
}
}
}
Error is : Cannot convert value of type 'String' to expected argument type 'Binding<String>'
However is I simply list accounts in a textfield it works. Like so:
var body: some View{
ZStack {
Form {
List(accounts, id: \.self) { account in
Text(account.email ?? "Unknown")
}
}
}
Why does the second code that uses List not give me the same error?
I thought it had something to do with the ?? operator but after research I realized that it perfectly fine to do given that email in my coredata object is String?.
Now my thought is that I am getting this error here because TextField needs a Binding wrapper? If that is true I'm not sure how to get this to work. All I want to do is have this TextField pre-populated with the single email record the FetchRequest retrieves from my Account Core Data object.
Thanks.
Edit: I want to add that I have found this post https://www.hackingwithswift.com/quick-start/swiftui/how-to-fix-cannot-convert-value-of-type-string-to-expected-argument-type-binding-string
and now I think what I need to do is store this account.email result into a State variable. My question still remains however, I'm not sure how to do this as I am looking for clean way to do it right in the view stack here. Thanks again.
TextField needs a binding to a string variable. Account is and ObservableObject, like all NSManagedObjects, so if you refactor your TextField into a separate View you could do this:
Form {
Section(header: Text("EMAIL")) {
ForEach(accounts, id: \.self) { account in
Row(account: account)
}
}
}
...
struct Row: View {
#ObservedObject var account: Account
var body: some View {
TextField("Email", text: $account.email)
}
}
Note the $account.email — the $ turns this into a binding.
Unfortunately, this new code also fails, because email is a String? (i.e., it can be nil) but TextField doesn’t allow nil. Thankfully that’s not too hard to fix, because we can define a custom Binding like so:
struct Row: View {
#ObservedObject var account: Account
var email: Binding<String> {
Binding<String>(get: {
if let email = account.email {
return email
} else {
return "Unknown"
}
},
set: { account.email = $0 })
}
var body: some View {
TextField("Email", text: email)
}
}
Notice we don’t have a $ this time, because email is itself a Binding.
This answer details getting a CoreData request into a #State variable:
Update State-variable Whenever CoreData is Updated in SwiftUI
What you'll end up with is something like this:
#State var text = ""
In your view (maybe attached to your ZStack) an onReceive property that tells you when you have new CoreData to look at:
ZStack {
...
}.onReceive(accounts.publisher, perform: { _ in
//you can reference self.accounts at this point
//grab the index you want and set self.text equal to it
})
However, you'll have to do some clever stuff to make sure setting it the first time and then probably not modifying it again once the user starts typing.
This could also get complicated by the fact that you're in a list and have multiple accounts -- at this point, I may split every list item out into its own view with a separate #State variable.
Keep in mind, you can also write custom bindings like this:
Binding<String>(get: {
//return a value from your CoreData model
}, set: { newValue in })
But unless you get more clever with how you're returning in the get section, the user won't be able to edit the test. You could shadow it with another #State variable behind the scenes, too.
Finally, here's a thread on the Apple Developer forums that gets even more in-depth about possible ways to address this: https://developer.apple.com/forums/thread/128195

How to create layout elements in Orchard 1.9

Can someone please guide me on how to create layout elements in Orchard 1.9. I couldn't find any resource online.
In general, creating a new layout element is similar to creating a new part. There is a driver and a few views involved in the process. To the point - you need to implement as follows:
An element class.. Class that inherits from Element, which contains all the element data. A model, so to speak.
A driver. Class that inherits from ElementDriver<TElement>, where TElement is the type you created above. Each element has it's own driver that handles displaying admin editor (and the postback) and frontend display views.
Shapes. All shapes should be placed under /Views/Elements/ folder, by convention.
Display shape. Named after your element, ie. MyElement.cshtml. This one renders your element on frontend.
Design display shape.. Named after your element, with .Design suffix, ie. MyElement.Design.cshtml. This one renders your element inside the layout editor.
Editor shape.. This one should be put in /Views/EditorTemplates/ folder instead. Default naming convention is Elements.MyElement.cshtml. It renders the editor shown when you drop a new element on layout editor canvas.
With all above done, your new element should appear in the list of elements on the right side of the layout editor, ready to use.
If you want to do some more complex elements, please check the existing implementations. Layouts module has a very decent architecture so you should get up to speed pretty quickly. Just keep in mind the necessary steps I wrote above.
To create a custom layout element first create a class that inherits from Element. Element is found in the Orchard.Layouts namespace so you need to add a reference. To follow Orchard standards put this file in a folder called Elements.
public class MyElement : Element
{
public override string Category
{
get { return "Content"; }
}
public string MyCustomProperty
{
get { return this.Retrieve(x => x.MyCustomProperty); }
set { this.Store(x => x.MyCustomProperty, value); }
}
}
Next, create a driver class in a folder called Drivers. This class inherits from ElementDriver<TElement> and likely you will want to override the OnBuildEditor and OnDisplaying methods. OnBuildEditor is used for handling creating our editors shape and updating our database when the editor is saved. OnDisplaying is used when we need to do things when displaying our element. Oftentimes, you will want to add properties to the shape which can be done with context.ElementShape.MyAdditionalProperty = "My Value";
public class MyElementDriver : ElementDriver<MyElement>
{
protected override EditorResult OnBuildEditor(MyElement element, ElementEditorContext context)
{
var viewModel = new MyElementEditorViewModel
{
MyCustomProperty = element.MyCustomProperty
};
var editor = context.ShapeFactory.EditorTemplate(TemplateName: "Elements.MyElement", Model: viewModel);
if (context.Updater != null)
{
context.Updater.TryUpdateModel(viewModel, context.Prefix, null, null);
element.MyCustomProperty = viewModel.MyCustomProperty;
}
return Editor(context, editor);
}
protected override void OnDisplaying(Reddit element, ElementDisplayContext context)
{
context.ElementShape.MyAdditionalProperty = "My Value";
}
}
We then just need our views. Our editor view goes into Views/EditorTemplates. The file name needs to be what we set the template name of the editor shape. In our case the view name will be Elements.MyElement.cshtml.
#model MyNameSpace.ViewModels.MyElementEditorViewModel
<fieldset>
<div>
#Html.LabelFor(m => m.MyCustomProperty, T("My Custom Property"))
#Html.TextBoxFor(m => m.MyCustomProperty, new { #class = "text medium" })
</div>
</fieldset>
Finally, we just need a view for our frontend. This view goes into the following folder Views/Elements. The name of the view file is the same as our element class name. For this example the file would be called MyElement.cshtml.
#using MyNameSpace.Elements
#using MyNameSpace.Models
#{
var element = (MyElement)Model.Element;
}
<h1>#element.MyCustomProperty</h1>
You will then have a new element that you can drag into your layout with the layout editor.
For more details on creating an element from start to finish check out my blog post on creating a Reddit element.

JointJs creating custom shapes and specifying their default link

I'm trying to implement my own shape class with ports. However I want the links that these shapes generate to be smooth. Right now, the only way i know to make smooth links is
link.set('smooth', true).
But how do i do that not through code? How do i get smooth links by just dragging?
I extended Link class (MyLink) but how do i tell JointJS which Link class to use when i drag on the port?
joint.shapes.myclass.Link = joint.dia.Link.extend({
defaults: {
type: 'myclass.Link',
attrs: { '.connection' : { 'stroke-width' : 5 }},
smooth:true
}
});
Links created via the UI by dragging ports are defined in the defaultLink property of the paper. It can either be an object in which case it is a link model or a function that returns a link model:
var paper = new joint.dia.Paper({
defaultLink: new joint.shapes.myclass.Link,
...
})
or:
var paper = new joint.dia.Paper({
defaultLink: function(elementView, magnet) {
if (aCondition) return new joint.dia.Link;
else return joint.shapes.myclass.Link;
}
})
The function gives you flexibility in creating different links on the fly depending on what element is underneath or what magnet (SVG element for port) is being dragged.

Add a MediaPicker to the General Site Settings

The current project I'm on is utilizing tenant sites. With each site, we want the ability to change the logo through out the tenant site by modifying the its settings (on the admin page, settings > general).
I've added two text fields to the site settings by following this well documented tutorial. However, I'd like the user to be able to pick the logos using the media picker instead of typing in the path.
Currently I have a LogoBarSettings part with its record, driver and handler. I'm not sure how to add the media picker to the my LogoBarSettings and even if I did, must I also create another handler, driver, and record for it? I can't imagine I would but I'm pretty stuck at this point.
Can someone provide some direction on this?
Here is my LogoBarSettings
public class LogoBarSettings : ContentPart<LogoBarSettingsPartRecord>
{
public string ImageUrl
{
get { return Record.ImageUrl; }
set { Record.ImageUrl = value; }
}
public string ImageAltText
{
get { return Record.ImageAltText; }
set { Record.ImageAltText = value; }
}
}
The MediaPicker is invoked through Javascript, so you shouldn't need to change any of your model classes. When the MediaPicker is loaded for a page, it sets up a jQuery event handler for all form elements on the page. Triggering the event orchard-admin-pickimage-open will open the MediaPicker. Supply a callback function to capture the picked media.
Here is a quick example that you can run in Firebug or Chrome Developer Tools from a page which has the MediaPicker loaded, such as a Page editor:
$('form').trigger("orchard-admin-pickimage-open", {
callback: function(data) {
console.log(data);
}})
This should print something similar to this:
Object {img: Object}
img: Object
align: ""
alt: ""
class: ""
height: "64"
html: "<img src="/Media/Default/images/test.jpg" alt="" width="64" height="64"/>"
src: "/Media/Default/images/test.jpg"
style: ""
width: "64"
__proto__: Object
__proto__: Object
The BodyPart editor integrates Orchard's MediaPicker with TinyMce, so you can start looking at that module for a more complete example, specifically Modules\TinyMce\Scripts\plugins\mediapicker\editor_plugin_src.js.

Resources