How to load angular-formly vm.fields from server - javascript

i am trying to use Angular-Formly to build dynamically forms starting from a set of .NET classes.
I serialize the class properties information in json and return that to Formly, but no fields is shown.
I follow suggestions find in: How to load angular-formly vm.fields object from remotely-generated json?
but not seems to work for me.
My form code is:
<form ng-submit="vm.onSubmit()" novalidate>
<formly-form model="vm.model" fields="vm.formFields">
<button type="submit" class="btn btn-primary submit-button">Submit</button>
</formly-form>
</form>
The angular code is:
<script>
/* global angular */
(function () {
'use strict';
var app = angular.module('formlyExample', ['formly', 'formlyBootstrap'], function config(formlyConfigProvider) {
// set templates here
//formlyConfigProvider.setType({
// name: 'custom',
// templateUrl: 'custom.html'
//});
});
app.factory('User', function ($http) {
return {
getFields: getFields
};
function getFields() {
return $http.post('TestFormly.aspx/LoadData', { headers: { 'Cache-Control': 'no-cache' } });
}
});
app.controller('MainCtrl', function MainCtrl($scope, $http, User) {
var vm = this;
// funcation assignment
vm.onSubmit = onSubmit;
vm.loadingData = User.getFields().then(function (result) {
vm.fields = JSON.parse(result.data.d);
vm.originalFields = angular.copy(vm.fields);
});
vm.model = {
};
// function definition
function onSubmit() {
alert(JSON.stringify(vm.model), null, 2);
}
});
})();
</script>
The CSharp.Net code is:
[WebMethod]
public static string LoadData()
{
string retValue = null;
List<FieldItem> m_fields = new List<FieldItem>();
FieldItem item1 = new FieldItem();
item1.key = "text";
item1.type = "input";
item1.templateOptions = new TemplateOptions() { label = "Text", placeholder = "Formly is terrific!" };
FieldItem item2 = new FieldItem();
item2.key = "story";
item2.type = "textarea";
item2.templateOptions = new TemplateOptions() { label = "Some sweet story", placeholder = "It allows you to build and maintain your forms with the ease of JavaScript :-)" };
m_fields.Add(item1);
m_fields.Add(item2);
retValue = JsonConvert.SerializeObject(m_fields);
return retValue;
}
The JSON result is:
[
{
"key":"text",
"type":"input",
"templateOptions":{
"label":"Text",
"placeholder":"Formly is terrific!"
}
},
{
"key":"story",
"type":"textarea",
"templateOptions":{
"label":"Some sweet story",
"placeholder":"It allows you to build and maintain your forms with the ease of JavaScript :-)"
}
}
]
Debugging with firebug i see the JSON passed correctly to the vm.fields but no input box is shown, only the Sumbit button.
I noticed that nor the Formly example shows the fields.
Can you help ?
Thanks in advance,
Giuseppe.

Here is a solution I've quickly hacked as a proof of concept for myself.
Basically the FormlyModelBuilder does scan a ViewModel class and builds a formly fields model.
Sample usage
public IActionResult Index()
{
return Ok(new ViewModels.Account.FormlyModelBuilder<ViewModels.Account.RegisterViewModel>().JsonStringify(new ViewModels.Account.RegisterViewModel { Email = "test#test.com" }));
}
Transforms this
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare ("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
into this
{
"fields": [{
"key": "Email",
"type": "email",
"templateOptions": {
"isRequired": false,
"label": "Email"
},
"expressionProperties": {
"templateOptions.focus": "Email"
}
}, {
"key": "Password",
"type": "password",
"templateOptions": {
"isRequired": false,
"label": "Password"
},
"expressionProperties": {}
}, {
"key": "ConfirmPassword",
"type": "password",
"templateOptions": {
"label": "Confirm password"
},
"expressionProperties": {}
}],
"model": {
"email": "test#test.com"
},
"expressionProperties": {}
}
Source Code
public class FormlyModelBuilder<T>
{
internal T GetAttributeFrom<T>(object instance, string propertyName) where T : Attribute
{
var property = instance.GetType().GetProperty(propertyName);
return GetAttributeFrom<T>(property);
}
internal T GetAttributeFrom<T>(PropertyInfo property) where T : Attribute
{
var attrType = typeof(T);
T t = (T)property.GetCustomAttributes(attrType, false).FirstOrDefault();
if (t == null)
{
var metaAttr = (MetadataTypeAttribute[])property.ReflectedType.GetCustomAttributes(typeof(MetadataTypeAttribute), true);
if (metaAttr.Length > 0)
{
foreach (MetadataTypeAttribute attr in metaAttr)
{
var subType = attr.MetadataClassType;
var pi = subType.GetField(property.Name);
if (pi != null)
{
t = (T)pi.GetCustomAttributes(attrType, false).FirstOrDefault();
return t;
}
}
}
}
else
{
return t;
}
return null;
}
internal FormlyModel<T> Build(T dataModel)
{
if (dataModel == null) throw new ArgumentNullException(nameof(dataModel));
//
var modelType = typeof(T);
var model = new FormlyModel<T>(dataModel);
foreach (var property in modelType.GetProperties(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Public))
{
var type = GetAttributeFrom<DataTypeAttribute>(property);
var field = new FormlyField(property.Name, GetInputTypeFromDataType(type?.DataType, property.PropertyType));
model.AddField(field);
//
var display = GetAttributeFrom<DisplayAttribute>(property);
field.TemplateOptions.Label = display?.Name;
//
var required = GetAttributeFrom<RequiredAttribute>(property);
field.TemplateOptions.IsRequired = required?.AllowEmptyStrings;
//
}
var focusField = model.Fields.First();
focusField.ExpressionProperties["templateOptions.focus"] = focusField.Key;
return model;
}
internal string JsonStringify(T dataModel)
{
if (dataModel == null) throw new ArgumentNullException(nameof(dataModel));
//
var dcr = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
dcr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
//
return Newtonsoft.Json.JsonConvert.SerializeObject(Build(dataModel),
new Newtonsoft.Json.JsonSerializerSettings
{
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
ContractResolver = dcr
});
}
private string GetInputTypeFromDataType(DataType? dataType, Type propertyType)
{
if (dataType != null)
{
//
switch (dataType)
{
case DataType.Text:
return "input";
case DataType.Password:
return "password";
case DataType.EmailAddress:
return "email";
case DataType.Html:
case DataType.MultilineText:
case DataType.Custom:
case DataType.DateTime:
case DataType.Date:
case DataType.Time:
case DataType.Duration:
case DataType.PhoneNumber:
case DataType.Currency:
case DataType.Url:
case DataType.ImageUrl:
case DataType.CreditCard:
case DataType.PostalCode:
case DataType.Upload:
default:
break;
}
}
switch (propertyType.Name)
{
case nameof(System.Boolean):
return "checkbox";
default:
return "input";
}
}
}
internal class FormlyModel<T>
{
internal FormlyModel(T dataModel)
{
if (dataModel == null) throw new ArgumentNullException(nameof(dataModel));
//
this.Fields = new List<FormlyField>();
this.Model = dataModel;
this.ExpressionProperties = new Dictionary<string, string>();
}
internal IEnumerable<FormlyField> Fields { get; }
internal T Model { get; }
internal Dictionary<string, string> ExpressionProperties { get; }
internal void AddField(FormlyField field)
{
if (field == null) new ArgumentNullException(nameof(field));
//
((List<FormlyField>)this.Fields).Add(field);
}
}
internal class FormlyField
{
internal FormlyField(string key, string type)
{
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key));
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentNullException(nameof(type));
//
TemplateOptions = new TemplateOptions();
ExpressionProperties = new Dictionary<string, string>();
Key = key;
Type = type;
}
internal string Key { get; }
internal string Type { get; }
internal string HideExpression { get; set; }
internal TemplateOptions TemplateOptions { get; }
internal Dictionary<string, string> ExpressionProperties { get; }
}
internal class TemplateOptions
{
public bool? IsRequired { get; set; }
public string Label { get; set; }
public string Placeholder { get; set; }
}

Apparently, the version of Angular-Formly (6.0.0-beta.1) being used in the example is throwing exception. I remember this was working before. Any way I reverted it to a stable version and its working again.
Here is the jsbin with your formly json that is working as it should:
http://jsbin.com/towozegiqu/edit

Related

How to dynamically set id to ajax.Actionlink in asp.net?

I have a list of items and in each item I have a Ajax.ActionLink what I want to do is to set id of each action link dynamically (Item id).
#Ajax.ActionLink("Join","ajaxview",new{ id = tour.TourId},newAjaxOption
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "currentaction"},new{
#class= "tm-tours-box-1-link-right",
#id="currentaction"})
my model is
HolidayPlanners.Models.Tour
what I want to do is something like this
#class= "tm-tours-box-1-link-right",
#id=#Tour.id
but it is giving me errors because I am using razon syntax(server side) inside jquery(client side) is there any way around to this?
Dynamic action link ajax, as I posted prior:
#car.CarMake,
"getUpdate",
new { carId = car.CarId },
new AjaxOptions
{
UpdateTargetId = "result" + car.CarId, //use car.ID here? not sure
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET"
}, new { #class = "getClick" })
Model:
#model IEnumerable<Testy20161006.Controllers.CarModel>
More about my model:
public class CarModel
{
public int CarId { get; set; }
public string CarMake { get; set; }
public string theCarModel { get; set; }
}
public class HomeController : Controller
{
public PartialViewResult getUpdate(int carId)
{
CarModel carModel = new CarModel();
switch (carId)
{
case 1:
carModel.CarId = 1;
carModel.CarMake = "updated11111Make";
carModel.theCarModel = "updated11111Model";
break;
case 2:
carModel.CarId = 2;
carModel.CarMake = "updated2Make";
carModel.theCarModel = "updated22222Model";
break;
case 3:
carModel.CarId = 3;
carModel.CarMake = "updated3Make";
carModel.theCarModel = "updated33333Model";
break;
default:
break;
}
return PartialView("_PartialView", carModel);
}
public ActionResult Index700()
{
IList<CarModel> carModelList = Setup();
return View(carModelList);
}
private static IList<CarModel> Setup()
{
IList<CarModel> carModelList = new List<CarModel>();
carModelList.Add(new CarModel { CarId = 1, CarMake = "VW", theCarModel = "model1" });
carModelList.Add(new CarModel { CarId = 2, CarMake = "BMW", theCarModel = "model2" });
carModelList.Add(new CarModel { CarId = 3, CarMake = "Ford", theCarModel = "model3" });
return carModelList;
}

How to pass in nested data from the client to the controller

I have a form that has two sections. 3 input fields and another section with 10 checkboxes.
public class Customerproductdto
{
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }
public string CustomerPhone { get; set; }
List<ProductDetails> GetAllChecked {get;set;}
}
public class ProductDetails
{
public string ProductName{ get; set; }
}
Here is jquery code I am using to get all the values of the checkboxes that were
checked on my form. They are about 10 and users could check everything.
var yourArray[]
$("input:checkbox[name=type]:checked").each(function(){
yourArray.push($(this).val());
});
Here is javascript that I use to collect the data and pass to my controller.
How can I pass in my array here all in one shot?
var objdata =
({
CustomerNumber: txtcustnumber,
CustomerName: txtcustname,
CustomerPhone: txtphone
//How do I pass the yourArray here?
});
var url = "#Url.Action("WriteToDb", "Home")";
var completeData = JSON.stringify({ 'Information': objdata });
$.get(url, { 'objdata': completeData }, function (data) {
$('#mainListContent').html(data);
});
Please note that I will like to deserialize this once I get to the controller.
Here is the method.
public ActionResult WriteToDb(string objdata)
{
Customerproductdto getAllTaskSaved = null;
try
{
var stripOffObjectName = JObject.Parse(objdata)["Information"];
var cleanedData = JsonConvert.DeserializeObject<Customerproductdto>(stripOffObjectName.ToString());
getAllTaskSaved = _dtcDataService.WriteTaskToDb(cleanedData, "Add");
}
catch (Exception ex)
{
logger.Error(ex);
}
return PartialView("_Taskdisplay", getAllTaskSaved);
}

KnockoutJS with MVC, Model and functions

I am working on a project which includes MVC and KnockoutJS. As this is the first time i'm working with KnockoutJS i'm running into a lot of issues.
I want to put my MVC Viewmodel on the webpage in a tabbed interface. Then from that interface i want to call functions depending on which button i press.
Currently i am getting the model to load and show itself fine, but i cannot call any functions i am trying to create.
Also i have no clue that the way i am creating this at the moment is the correct one.
I have the following model:
public partial class SSoSearchModel
{
public string SearchParameter { get; set; }
public string SearchValue { get; set; }
public string SearchLabel { get; set; }
public IQueryable<SsoRecordResultViewModel> searchResults { get; set; }
public List<SelectListItem> ParameterList { get; set; }
public List<SelectListItem> LabelList { get; set; }
}
The SearchResults look like this
public partial class SsoRecordResultViewModel
{
[JsonProperty(PropertyName = "first_name")]
public string FirstName { get; set; }
[JsonProperty(PropertyName = "last_name")]
public string LastName { get; set; }
[JsonProperty(PropertyName = "email_address"), DefaultValue(PropertyName = "email_addresses")]
public string EmailAddress { get; set; }
[JsonProperty(PropertyName = "phone_number"), DefaultValue(PropertyName = "phone_numbers")]
public string PhoneNumber { get; set; }
[JsonProperty(PropertyName = "Klantnummer"), CustomAttribute(PropertyName = "Klantnummer")]
public string Klantnummer { get; set; }
[JsonProperty(PropertyName = "verzekerdenummer"), CustomAttribute(PropertyName = "Verzekerdennummer")]
public string Verzekerdenummer { get; set; }
[JsonProperty(PropertyName = "person_id")]
public string SSOID { get; set; }
[JsonProperty(PropertyName = "initials")]
public string Initialen { get; set; }
}
And this is all loaded in the Index function of the controller with dummy data
public ActionResult Index()
{
List<SsoRecordResultViewModel> temp = new List<SsoRecordResultViewModel>();
temp.Add(new SsoRecordResultViewModel { SSOID = "ea373d27-142d-48f6-86c9-dcdb15e316d2", EmailAddress = "A", FirstName = "a", Klantnummer = "1", Verzekerdenummer = "1", PhoneNumber = "1", Initialen = "A", LastName = "a" });
temp.Add(new SsoRecordResultViewModel { SSOID = "2d613aba-3f89-43b0-919a-1aa615086ff3", EmailAddress = "b", FirstName = "b", Klantnummer = "2", Verzekerdenummer = "2", PhoneNumber = "2", Initialen = "b", LastName = "b" });
temp.Add(new SsoRecordResultViewModel { SSOID = "c142f22e-7664-4a9c-9303-293b48acbf65", EmailAddress = "c", FirstName = "c", Klantnummer = "3", Verzekerdenummer = "3", PhoneNumber = "3", Initialen = "c", LastName = "c" });
SSoSearchModel model = new SSoSearchModel();
model.searchResults = temp.AsQueryable(); //Enumerable.Empty<SsoRecordResultViewModel>().AsQueryable();
//zoekopties
model.ParameterList = new List<SelectListItem>();
model.ParameterList.Add(new SelectListItem { Text = "Zoek op Klantnummer", Value = "Klantnummer" });
model.ParameterList.Add(new SelectListItem { Text = "Zoek op Verzekerdenummer", Value = "Verzekerdenummer" });
model.ParameterList.Add(new SelectListItem { Text = "Zoek op E-mail adres", Value = "Email" });
model.ParameterList.Add(new SelectListItem { Text = "Zoek op SSO ID", Value = "SSOID" });
model.ParameterList.Add(new SelectListItem { Text = "Zoek op Telefoonnummer", Value = "Telefoonnummer" });
//Labels
model.LabelList = new List<SelectListItem>();
model.LabelList.Add(new SelectListItem { Text = "Rel1", Value = "Rel1" });
model.LabelList.Add(new SelectListItem { Text = "Rel2", Value = "Rel2" });
return View(model);
}
Now on the Index.cshtml i offcourse have my markup which shows the data great at the moment, but the KnockoutJS is the issue.
The javascript looks the following:
<script>
function ViewModel() {
var self = this;
self.Search = ko.observableArray(searchResults);
self.save = function(ssoid){
//ajax hier
};
};
var rawData = #Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model));
var Data = ko.mapping.fromJS(rawData,ViewModel);
ko.applyBindings(Data);
</script>
This currently shows me the data, but i do not know how i can call the save function. I have this but this does not work:
<button type="button" class="btn btn-default" aria-label="Bewaren" data-toggle="tooltip" data-placement="top" title="Bewaar de gegevens" data-bind="click: function() { $data.save(SSOID()); }"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
Also when i swap the values in ko.mapping.fromJS(rawData,ViewModel); the data does not get shown anymore.
Anyone here who can help me with this as it is driving me crazy.

model binding from ajax for date value passing as null

I have a page called bookprogram and below is its model!
public class BookViewModel
{
[Required(ErrorMessage = "Field cannot be blank!", AllowEmptyStrings = false)]
[Display(Name="Name *")]
public string name { get; set; }
[Required(ErrorMessage = "Field cannot be blank!", AllowEmptyStrings = false)]
[DataType(DataType.PhoneNumber)]
public string contact { get; set; }
[Required(ErrorMessage = "Field cannot be blank!", AllowEmptyStrings = false)]
[RegularExpression("[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+.[A-Za-z]{2,4}", ErrorMessage = "Invalid Email Id")]
public string email { get; set; }
[Required(ErrorMessage = "Please select a category")]
public string category { get; set; }
[Required(ErrorMessage = "Field cannot be blank!", AllowEmptyStrings = false)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime dateofprogram { get; set; }
[Required(ErrorMessage = "Field cannot be blank!", AllowEmptyStrings = false)]
[StringLength(200,ErrorMessage="Max length exceeded! Should be less than 200 characters")]
public string Message { get; set; }
}
Here is my js for performing AJAX Post
function ValidateBookProgramAndPost(form)
{
$(form).on("submit", function (e) {
e.preventDefault();
ValidateForm(form);
var selectedVal = $(form).find('select').children(":selected").val();
if(selectedVal=="")
{
$(form).find('div.bootstrap-select').children(":first").addClass('alert-danger');
$(form).find('div.bootstrap-select').next('.text-danger').html('Please select a category!');
}
var dateofprog = moment().format($('#txtDate').val());
console.log(dateofprog);
$.ajax({
url: '/BookProgram/',
type: "POST",
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ model:{
name: $('#txtName').val(),
contact: $('#txtPhone').val(),
email: $('#txtEmail').val(),
category: $("#hdnSelectedCategory").val(),
dateofprogram: dateofprog,
Message: $("#txtMessage").val()
}
}),
success: function (data) {
if (data.result) {
$(form).find('input[type=text], textarea').val('').removeClass("alert-success");
$('.selectpicker').selectpicker('refresh');
}
else {
if (data.result == "Email Validation failed on server side!") {
$("#txtEmail").addClass("alert-danger");
}
else {
//display error message
}
return false;
}
return true;
}
}
$(form).unbind('submit');
return false;
});
and here is my controller:
[HttpPost]
public ActionResult BookProgram(BookViewModel model)
{
bool valid = false;
bool val = false;
if (ModelState.IsValid)
{
if (model.name != "" && model.category != "" && model.contact != "" && model.dateofprogram.ToShortDateString() != "" && model.email != "" && model.Message != "")
{
if (v.validateEmail(model.email) && v.validatePhone(model.contact))
{
valid = true;
}
else
{
return Json(new { result = "Email/Phone Validation failed on server side!" });
}
}
else
{
return Json(new { result = "One of the field has been modified and has been sent empty!!" });
}
if (valid)
{
using (var context = new MConnectionString())
{
tbl_programs book = new tbl_programs();
book.cont = model.contact;
book.date = model.dateofprogram;
book.email = model.email;
book.msg = model.Message;
book.category = model.category;
book.status = "";
book.name = model.name;
context.tbl_programs.Add(book);
context.SaveChanges();
val = true;
}
if (val)
{
return Json(new { result = true });
}
else
{
return Json(new { result = "Could not book the program. Please try again!" });
}
}
return Json(new { result = "Could not book the program. Please try again!" });
}
return Json(new { success = false });
}
But when I check in the controller the date value is coming null and Model.IsValid fails. how I should pass date value from ajax then? Console.log shows selected date[dateofprog] as "14/02/2015"[example] but it will not be assigned to model. Where is the problem actually I cannot make it out. Can anyone help me on this??
You are posting a invalid format for dateofprogram. It cases a failure at the binding process. You must specify the culture in at system.web section of the web.config to get the right date parsed from the json, for exemple:
<globalization uiCulture="pt-BR" culture="pt-BR" />
The code above informs the culture of the application, then if I inform a DateTime in BR format like dd/MM/yyyy it will bind correctly.

Breeze createEntity Type not recognized

I'm trying to make my call to the server with BreezeJS but can't get it to work. It says tblMovie is not recognized. I can't find the problem :S
When I want to add a new movie it says so.
show.js
self.viewAddMovieModal = function () {
self.app.showModal(new self.addmovie()).then(function (result) {
if (result != undefined) {
var movie = dataservice.createMovie({
Title: result[0].title,
Director: result[0].director
});
if (movie.entityAspect.validateEntity()) {
self.movies.push(new movie(result[0].title, result[0].director));
dataservice.saveChanges();
} else {
alert("Error");
}
}
});
};
My dataservice.js layer
/// <reference path="../../Scripts/breeze.debug.js"/>
define(["require"], function (require) {
var Dataservice = (function () {
function Dataservice(service) {
this.serviceName = '';
this._isSaving = false;
this.serviceName = service;
this.Manager = new breeze.EntityManager(this.serviceName);
this.EntityQuery = new breeze.EntityQuery();
}
Dataservice.prototype.getAllMovies = function () {
this.EntityQuery = breeze.EntityQuery.from("AllMovies");
return this.Manager.executeQuery(this.EntityQuery);
};
Dataservice.prototype.createMovie = function (initialValues) {
return this.Manager.createEntity('tblMovies', initialValues); //THis is where it goes wrong :(
};
Dataservice.prototype.saveChanges = function (suppressLogIfNothingToSave) {
if (this.Manager.hasChanges()) {
if (this._isSaving) {
setTimeout(this.saveChanges, 50);
return;
}
this.Manager.saveChanges().then(this.saveSucceeded).fail(this.saveFailed).fin(this.saveFinished);
} else if (!suppressLogIfNothingToSave) {
}
};
Dataservice.prototype.saveSucceeded = function (saveResult) {
this._isSaving = false;
};
Dataservice.prototype.saveFailed = function (error) {
};
Dataservice.prototype.saveFinished = function () {
this._isSaving = false;
};
return Dataservice;
})();
return Dataservice;
})
I do have a model tblMovie
using System;
using System.ComponentModel.DataAnnotations;
namespace DurandalMovieApp.Models
{
public class tblMovie
{
[Key]
public int MovieID { get; set; }
public string Title { get; set; }
public string Director { get; set; }
}
}
Hope someone can help!
I think that the problem is that your entity is: tblMovie, not tblMovies.
Try replacing:
return this.Manager.createEntity('tblMovies', initialValues);
With:
return this.Manager.createEntity('tblMovie', initialValues);

Categories

Resources