Goal: fetch Fetch core data object into a SwiftUI picker
Problem: haven't figured out how to pass data into a Picker
import SwiftUI
struct DetailView: View {
#ObservedObject var order: Order
#State var showOrderEdit = false
var body: some View {
Form{
Text(order.flavor)
Text(order.tableNumber)
}
.navigationTitle(order.pizzaType)
.toolbar {
ToolbarItem(placement: .primaryAction) {
//edit button
Button(action: {
showOrderEdit = true
}, label: {
Text("Edit")
})
.sheet(isPresented: $showOrderEdit) {
DetailEdit(order: order)
}
}
}
}
}
import SwiftUI
struct DetailEdit: View {
#State var tableNumber = ""
#ObservedObject var order: Order
#Environment(\.managedObjectContext) private var viewContext
#Environment (\.presentationMode) var presentationMode
#State var selectedFlavor = Flavor.chocolate
enum Flavor: String, CaseIterable, Identifiable {
case chocolate
case vanilla
case strawberry
var id: String { self.rawValue }
}
var body: some View {
NavigationView {
Form {
//1- this work fine
TextField("table number", text: $tableNumber)
//2- this doesnt get updated
Picker("Flavor", selection: $selectedFlavor) {
Text("Chocolate").tag(Flavor.chocolate)
Text("Vanilla").tag(Flavor.vanilla)
Text("Strawberry").tag(Flavor.strawberry)
}
Text("Selected flavor: \(selectedFlavor.rawValue)")
}
//fetch data from core data -> item edit
.onAppear {
//1- this work fine
self.tableNumber = self.order.tableNumber
//2- this doesnt get updated
self.selectedFlavor.rawValue = self.order.flavor
}
.navigationTitle("Edit Order")
}
}
Try this:
.onChange(of: selectedFlavor) {
self.order.flavor = $0.rawValue
}
.onAppear {
self.tableNumber = self.order.tableNumber
self.selectedFlavor = Flavor(rawValue: self.order.flavor)
}
Related
In my Angular app I am trying to save image and text inside a table. Everything is working fine. I can add the the data, I can get the data. But the problem is if I click on save, data shows inside the table but only texts can be seen without refresh, image is showing the alt image value.
But if I refresh the page the page it works perfectly.
and in the table I can see something like this,
Here is my service file:
#Injectable({
providedIn: 'root'
})
export class CategoriesService {
private categories:Category[] = [];
private categoryUpdated = new Subject<Category[]>();
constructor(private http : HttpClient, private router: Router) { }
getUpdateListener(){
return this.categoryUpdated.asObservable();
}
/* posting request */
addCategory(name: string, image: File){
const categoryData = new FormData();
categoryData.append('name', name);
categoryData.append('image',image, name);
this.http.post<{message : string, category: Category}>(
'http://localhost:3000/api/v1.0/categories',categoryData
).subscribe(responseData=>{
const category : Category = {
id: responseData.category.id,
name : name,
image : responseData.category.image
}
this.categories.push(category);
this.categoryUpdated.next([...this.categories]);
})
}
/* getting categories, data must be as backend i.e message and object */
getCategories(){
this.http.get<{message: string; categories: any}>(
"http://localhost:3000/api/v1.0/categories"
)
.pipe(map((cateData)=>{
return cateData.categories.map(category=>{
return {
id: category._id,
name : category.name,
image: category.image
}
})
}))
.subscribe(transformedCate =>{
this.categories = transformedCate;
this.categoryUpdated.next([...this.categories])
})
}
}
And my main component.ts file:
export class CategoriesComponent implements OnInit,OnDestroy{
togglePanel: any = {};
categoryPanel: any = {};
categories : Category[] = [];
private categorySub : Subscription;
constructor(private _categoriesService : CategoriesService, private dialog : MatDialog){}
ngOnInit(){
this._categoriesService.getCategories();
this.categorySub = this._categoriesService.getUpdateListener().subscribe((cate: Category[])=>{
this.categories = cate;
})
}
OnFormOpen(){
this.dialog.open(CategoryFormComponent)
}
ngOnDestroy(){
this.categorySub.unsubscribe();
}
}
And my form component:
export class CategoryFormComponent implements OnInit {
form : FormGroup;
imagePreview : string;
constructor(private dialogRef : MatDialogRef<CategoryFormComponent>,
#Inject (MAT_DIALOG_DATA) private data : any,
private _categoriesService : CategoriesService) {}
onCancel(){
this.dialogRef.close();
}
ngOnInit(): void {
this.form = new FormGroup({
name : new FormControl(null,{validators:[Validators.required, Validators.minLength(3)]}),
image : new FormControl(null,{validators: [Validators.required], asyncValidators : [mimeType]})
})
}
/*event for checking the image after load */
onImgPicked(event : Event){
const file = (event.target as HTMLInputElement).files[0];
this.form.patchValue({image: file});
this.form.get('image').updateValueAndValidity();
// console.log(file);
// console.log(this.form)
const reader = new FileReader();
reader.onload = () =>{
this.imagePreview = reader.result as string;
};
reader.readAsDataURL(file);
}
/*On category added */
OnCategoryAdded(){
//is loading
this._categoriesService.addCategory(this.form.value.name, this.form.value.image);
this.form.reset();
this.dialogRef.close();
}
}
Setting a timeout on ngOnInit works but I want to make it without settiimeout
setTimeout(() => {
this.OnInit();
},10000)
}
It looks you are misunderstanding the properties of the http response in addCategory. Try modifying as below.
addCategory(name: string, image: File){
const categoryData = new FormData();
categoryData.append('name', name);
categoryData.append('image',image, name);
this.http.post<{message : string, category: Category}>(
'http://localhost:3000/api/v1.0/categories',categoryData
).subscribe(responseData=>{
const category : Category = {
id: responseData._doc.id, // here
name : name,
image : responseData._doc.image // here
}
this.categories.push(category);
this.categoryUpdated.next([...this.categories]);
})
}
Previously, I was using FetchRequest and passing the results to my view. I had this working in my preview using the following code:
struct SchoolsGrid_Previews: PreviewProvider {
static var viewContext = PersistenceController.preview.container.viewContext
static var fetchRequest = School.fetchRequest()
static func getSchools() -> [School] {
do {
return try viewContext.fetch(fetchRequest)
} catch {
print(error.localizedDescription)
return []
}
}
static var previews: some View {
SchoolsGrid(sectionedSchools: Dictionary(
grouping: getSchools(),
by: {
$0.name!.first!.isLetter ? String($0.name!.first!) : "#"
}
))
.environment(\.managedObjectContext, viewContext)
.environmentObject(SettingsData())
}
}
After learning about SectionedFetchRequest, I updated my code and my view to accept this instead, but I am unable to find the "sectioned" alternative to the code above. I tried the following without any luck - it returns no data:
struct SchoolsGrid_Previews: PreviewProvider {
static var viewContext = PersistenceController.preview.container.viewContext
static var fetchRequest = SectionedFetchRequest(fetchRequest: School.fetchRequest(), sectionIdentifier: \School.firstLetterOfName)
static var previews: some View {
SchoolsGrid(sectionedSchools: fetchRequest.wrappedValue)
.environment(\.managedObjectContext, viewContext)
.environmentObject(SettingsData())
}
}
Edit to add:
I have a wrapper view which fetches my data, because the same data can be presented in two different subviews - SchoolsGrid and SchoolsList:
struct Schools: View {
#State var isGrid = false
#State var searchTerm: String = ""
#SectionedFetchRequest<String, School>(
entity: School.entity(),
sectionIdentifier: \.firstLetterOfName,
sortDescriptors: [
NSSortDescriptor(keyPath: \School.name, ascending: true),
]
) var sectionedSchools: SectionedFetchResults<String, School>
var query: Binding<String> {
Binding {
searchTerm
} set: { newValue in
searchTerm = newValue
let namePredicate = NSPredicate(format: "%K CONTAINS[c] %#", #keyPath(School.name), newValue)
sectionedSchools.nsPredicate = newValue.isEmpty
? nil
: namePredicate
}
}
var body: some View {
NavigationView {
ScrollViewReader { _ in
VStack {
ZStack {
if isGrid {
SchoolsGrid(sectionedSchools: sectionedSchools, searchTerm: searchTerm)
} else {
SchoolsList(sectionedSchools: sectionedSchools, searchTerm: searchTerm)
}
}
}
.searchable(text: query)
}
.navigationTitle("Schools")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: { isGrid.toggle() }) {
Image(systemName: isGrid ? "list.bullet" : "square.grid.2x2")
.accessibilityLabel("Toggle View")
}
}
}
}
}
}
My SchoolsGrid looks like:
struct SchoolsGrid: View {
var sectionedSchools: SectionedFetchResults<String, School>
var searchTerm: String
var gridItems: [GridItem] {
Array(repeating: .init(.adaptive(minimum: 120)), count: 2)
}
var body: some View {
ScrollView {
ForEach(sectionedSchools) { section in
Section(header: SchoolsGridSectionHeader(key: section.id)) {
LazyVGrid(columns: gridItems) {
ForEach(section) { school in
NavigationLink(destination: SchoolDetail(school: school)) {
SchoolsGridItem(school: school, searchTerm: searchTerm)
}
}
}
}
}
}
}
}
And my SchoolsList:
struct SchoolsList: View {
var sectionedSchools: SectionedFetchResults<String, School>
var searchTerm: String
var body: some View {
List {
ForEach(sectionedSchools) { section in
Section(header: Text(section.id).id(section.id)) {
ForEach(section) { school in
NavigationLink(destination: SchoolDetail(school: school)) {
SchoolsListItem(school: school, searchTerm: searchTerm)
}
}
}
}
}
.listStyle(PlainListStyle())
}
}
As with the first part of my question, I only recently changed the fetch request to a sectioned fetch request, so previously both SchoolsGrid and SchoolsList accepted schools: FetchResults< School> and I was able to emulate this in the Preview using the first code block, where I used viewContext.fetch. After updating to accept sectionedSchools: SectionedFetchResults<String, School>, there is no viewContext.sectionedFetch that I can use, so passing it the way I tried using:
var fetchRequest = SectionedFetchRequest(fetchRequest: School.fetchRequest(), sectionIdentifier: \School.firstLetterOfName)
returns no data because it isn't being called on the preview view context.
I'm new to building custom cards for HASS so this might be obvious.
I have a basic clock card and I have put a console message on the render method - it seems to be writing to the log even when the card is no longer being presented? (i.e. you've moved to another lovelace view).
I'm using a setTimeout to trigger a property change - am I meant to stop the timeout at some point of the lifecycle, or is there some teardown in the lifecycle, etc?
Here's my code:
/* eslint-disable #typescript-eslint/no-explicit-any */
import {
LitElement,
html,
customElement,
property,
CSSResult,
TemplateResult,
css,
PropertyValues,
internalProperty,
} from 'lit-element';
import {
HomeAssistant,
hasConfigOrEntityChanged,
hasAction,
ActionHandlerEvent,
handleAction,
LovelaceCardEditor,
getLovelace,
LovelaceCard,
} from 'custom-card-helpers'; // This is a community maintained npm module with common helper functions/types
import { hass, provideHass } from "card-tools/src/hass";
import './editor';
import type { BoilerplateCardConfig } from './types';
import { actionHandler } from './action-handler-directive';
import { CARD_VERSION } from './const';
import { localize } from './localize/localize';
/* eslint no-console: 0 */
console.info(
`%c BOILERPLATE-CARD \n%c ${localize('common.version')} ${CARD_VERSION} `,
'color: orange; font-weight: bold; background: black',
'color: white; font-weight: bold; background: dimgray',
);
// This puts your card into the UI card picker dialog
(window as any).customCards = (window as any).customCards || [];
(window as any).customCards.push({
type: 'boilerplate-card',
name: 'Boilerplate Card',
description: 'A template custom card for you to create something awesome',
});
// TODO Name your custom element
#customElement('boilerplate-card')
export class BoilerplateCard extends LitElement {
CUSTOM_TYPE_PREFIX = "custom:";
constructor() {
super();
this.date = new Date();
setInterval(() => {
this.date = new Date();
}, 1000);
}
public static async getConfigElement(): Promise<LovelaceCardEditor> {
return document.createElement('boilerplate-card-editor');
}
public static getStubConfig(): object {
return {};
}
// TODO Add any properities that should cause your element to re-render here
// https://lit-element.polymer-project.org/guide/properties
#property({ attribute: false }) public hass!: HomeAssistant;
#internalProperty() private date: Date;
#internalProperty() private config!: BoilerplateCardConfig;
// https://lit-element.polymer-project.org/guide/properties#accessors-custom
public setConfig(config: BoilerplateCardConfig): void {
// TODO Check for required fields and that they are of the proper format
if (!config) {
throw new Error(localize('common.invalid_configuration'));
}
if (config.test_gui) {
getLovelace().setEditMode(true);
}
this.config = {
name: 'Boilerplate',
...config,
};
}
// https://lit-element.polymer-project.org/guide/lifecycle#shouldupdate
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps, true);
}
// https://lit-element.polymer-project.org/guide/templates
protected render(): TemplateResult | void {
const timeFormatter: Intl.DateTimeFormatOptions = {
year: undefined,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}
console.info("Draw")
return html`
<ha-card
.header=${this.config.name}
.actionHandler=${actionHandler({
hasHold: hasAction(this.config.hold_action),
hasDoubleClick: hasAction(this.config.double_tap_action),
})}
tabindex="0"
.label=${`Boilerplate: ${this.config.entity || 'No Entity Defined'}`}
>
<h1>${new Intl.DateTimeFormat(undefined, timeFormatter).format(this.date)}</h1>
${this.config.cards.map((card) => {
let tag = card.type;
if (tag.startsWith(this.CUSTOM_TYPE_PREFIX)) {
tag = tag.substr(this.CUSTOM_TYPE_PREFIX.length);
} else {
tag = `hui-${tag}-card`;
}
const cardElement = document.createElement(tag) as LovelaceCard;
cardElement.setConfig(card);
cardElement.hass = hass();
return cardElement
})}
</ha-card>
`;
}
// https://lit-element.polymer-project.org/guide/styles
static get styles(): CSSResult {
return css``;
}
}
Use connectedCallback and disconnectedCallback to start and stop your timer:
#customElement('boilerplate-card')
export class BoilerplateCard extends LitElement {
connectedCallback() {
super.connectedCallback();
this.date = new Date();
this.interval = setInterval(() => {
this.date = new Date();
}, 1000);
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.interval);
}
...
}
struct ContentView: View {
#State var manager = HtpAuth()
var body: some View {
if manager.authenticated {
Text("Login Successful!!")
}
// 2 textfields for username and password and
// a login button to call checkForAuth function
//...
}
}
class HttpAuth: ObservableObject {
var didChange = PassthroughSubject<HttpAuth, Never>()
var authenticated = false {
didSet {
didChange.send(self)
}
}
func checkForAuth(username: String, password: String) {
//REST API call
URLSession.shared.data.task(with: loginRequest) { data, response, error in
guard let data = data else { return }
let finalData = try! JSONDecoder().decode(ServerMessage.self, from: data)
DispatchQueue.main.async {
self.authenticated = true
}
}.resume()
}
}
I am getting response from the server. But after recieving the resposne, I want to return to the main thread to display some other view. I am using DispatchQueue.main.async for this purpose, but it does not work. The label "Login Successfull!!" never appears, after successful login.
Use instead standard Published and ObservedObject wrappers as below
struct ContentView: View {
#ObservedObject var manager = HtpAuth() // use #StateObject in SwiftUI 2
...
class HttpAuth: ObservableObject {
#Published var authenticated = false
...
In Asp.Net MVC, you can easily return a partial view by doing the following:
return PartialView("ModelName", Model);
How is this done on a RazorPage ViewModel Handler?
I figured this out. It is not nearly as straight forward as it is in MVC. You have to create an empty ViewDataDictionary() and then set its Model property to the partial's populated model.
View Model / Handler
public async Task<IActionResult> OnGetAsyncUpdateSearchResults(DateTime startDate, DateTime endDate, string selectedTypes)
{
int[] types = selectedTypes.Split(",").Select(x => int.Parse(x)).ToArray();
var inventory = await _itemService.GetFiltered(types, null, null, null, null, null, null, startDate, endDate.ToUniversalTime(), null, null, null, null, null, null, null);
if (inventory != null)
{
SearchResultsGridPartialModel = new SearchResultsGridPartialModel();
SearchResultsGridPartialModel.TotalCount = inventory.TotalCount;
SearchResultsGridPartialModel.TotalPages = inventory.TotalPages;
SearchResultsGridPartialModel.PageNumber = inventory.PageNumber;
SearchResultsGridPartialModel.Items = inventory.Items;
}
var myViewData = new ViewDataDictionary(new Microsoft.AspNetCore.Mvc.ModelBinding.EmptyModelMetadataProvider(), new Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary()) { { "SearchResultsGridPartialModel", SearchResultsGridPartialModel } };
myViewData.Model = SearchResultsGridPartialModel;
PartialViewResult result = new PartialViewResult()
{
ViewName = "SearchResultsGridPartial",
ViewData = myViewData,
};
return result;
}
I can now call this handler via ajax GET and have it return the partial's HTML. I can then set the partial's div and the partial refreshes as expected.
Here is the AJAX call I'm making:
var jsonData = { "startDate": startDate, "endDate": endDate, "selectedTypes": selectedTypesAsString };
$.ajax({
type: 'GET',
url: "searchresults/?handler=AsyncUpdateSearchResults",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val());
},
contentType: 'application/json; charset=utf-8"',
data: jsonData,
success: function (result) {
$("#searchResultsGrid").html(result);
},
error: function (error) {
console.log(error);
}
});
Thanks alot to TechFisher for figuring it out, here is a bit cleaner example.
public IActionResult OnGetTestPartial()
{
return new PartialViewResult()
{
ViewName = "Test",
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = new TestPartialData { Data = "inputhere" },
}
};
}
Partial view in a file name "Test.cshtml" in the same folder as the above class.
#using YourNamespace
#model TestPartialData
<div>Hello, model value: #Model.Data</div>
Load it async with jquery
$("#someHtmlElementId").load("Your/Path/TestPartial");