ict.ken.be

 

Posts in Category: Javascript

Developer tools show only custom window properties 

Categories: Javascript

You can run this code in your browser console and then add a watch for the windowCustomized variable. It will have only custom added window properties or leaks.

windowCustomized = {};
var iframe = document.createElement('iframe');
iframe.onload = function() {
var standardGlobals = Object.keys(iframe.contentWindow);
for(var b in window) {
const prop = window[b];
if(window.hasOwnProperty(b) && prop && !prop.toString().includes('native code') && !standardGlobals.includes(b)) {
windowCustomized[b] = prop;
}
}
//console.info(windowCustomized)
};
iframe.src = 'about:blank';
document.body.appendChild(iframe);
//inspect(windowCustomized);

Javascript 

Categories: Javascript

Javascript and therefor Node have some concepts that are often misunderstood by many developers (eg. Java, C#, ... )

I decided to put some basic functionality tests in a visual studio solution, so you can see it all working.

Feel free to add more...

https://github.com/KenVanGilbergen/ken.Spikes.Javascript

Update:

Willy just pointed me to a nice free javascript book collection at https://github.com/getify/You-Dont-Know-JS

jQuery CDN fallback 

Categories: Javascript
<script>
!window.jQuery && document.write(unescape('%3Cscript src="/ClientScript/jquery/2.0.1/jquery-2.0.1.min.js"%3E%3C/script%3E'))
</script>
<script>
!window.jQuery.ui && document.write(unescape('%3Cscript src="/ClientScript/jquery-ui/1.10.3/jquery-ui.min.js"%3E%3C/script%3E'))
</script>

Building Data-Centric Single Apps with Breeze - Notes 

Categories: Javascript Notes

by Brian Noyes (brian.noyes@solliance.net)
www.solliance.net

Server Side

  • BreezeController vs OData vs client enriching

Query Calling Pattern

  • Client > executeQuery(query) > Entity Manager > GET Metadata from Controller (first time) > GET Query the controller > Return Query Results

SaveChanges Calling Pattern

  • Client > saveChanges > Entity Manager > POST Modified Entity ChangeSet > Controller > Return Server Persisted Entities

RPC, CRUD, REST, ODATA Services

  • Breeze routing is action-based (contains an entire model) > by default using a WebActivator.PreApplicationStartMethod(...)
  • OData query parameters are turned into an expression tree that is executed against the return IQueryable by the query filter
  • SaveChanges takes a JObject bundle with a batch of entities.

Extend EFContextProvider to add custom code

  • BeginSaveEntity, BeginSaveEntities (Derived or Delegated)
_ContextProvider.BeforeSaveEntitiesDelegate = BeforeSaveEntities;
private Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo> saveMap)
{
var validator = new ProductValidator();
foreach (var type in saveMap.Keys)
{
if (type == typeof(Product))
foreach (var productEntityInfo in saveMap[type])
{
validator.Validate((Product)productEntityInfo.Entity)
}
}
}

Working with OData Services

  • On client data.js is needed
  • Server needs to define Entity Data Model with same namespace as entity types namespace
  • Metadata returned from the server needs to include foreign key relation information
  • Need some changes to the way you setup Entity Data Model and return metadata from the server
public class ZzaODataService : DataService<ZzaEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Products", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
}

public static void RegisterRoutes(RouteCollection routes)
{
...
routes.IgnoreRoute("{resource}.svc/{*pathInfo}");
...
}

  • edmx > properties > Namespace: ZzaODataWeb (same project space as service)

app = {};
breeze.config.initializeAdapterInstance({dataService:"OData"});
em = new breeze.EntityManager("ZzaODataService.svc");

  • GET /ZzaODataService.svc/$metadata (returns xml)
  • GET /ZzaODataService.svc/Products
  • POST /ZzaODataService.svc/$batch (multi-part mime message)

Breeze Query Basics

app.em = new breeze.EntityManager("breeze/Zza");
breeze.EntityQuery.from("Products").where("Description","contains","bacon");
app.em.executeQuery(query).then(...).fail(...);

var p = new breeze.Predicate("Description","contains","bacon");
... .where(p);

var gteMay1 = new breeze.Predicate("OrderDate", "greaterThanOrEqual", moment("2013-05-01");
var lteMay31 = new breeze.Predicate("OrderDate", breeze.FilterQueryOp.LessThanOrEqual, moment("2013-05-31");
...
var predicate = breeze.Predicate.and(gteMay1, lteMay31, gt100);

... orderBy("Name");
... orderBy("TotalPrice, Order.OrderDate");
... orderBy("OrderDate desc").skip(currentPage() * pageSize).take(pageSize);

... orderBy("OrderDate desc").skip(currentPage() * pageSize).take(pageSize).inlineCount();

... select("LastName, Phone, State");
... from("OrderItems").where("Order.Customer.LastName","equals", queryInput()).select("Order.OrderDate, Product.Name, Quantity");
-> use Product_Name, Order_OrderDate to reference the projected elements.

... query.expand("Orders,Orders.OrderDetails,Orders.OrderDetails.Product"); //eagerloading

Breeze Query Advanced

Pre-fetching Metadata is needed when getting by id or creating.
... EntityManager.fetchMetadata().fail(...);

... EntityManager.fetchEntityByKey("Products", key).then(...);


public object Lookups()
{
var products = _ContextProvider.Context.Products.ToList();
...
return { products, productOptions };
}
breeze.EntityQuery.from("Lookups");
... data.results[0].products
... data.results[0].productOptions

public IQueryable<OrderItem> OrderItemsWithCouponCode(string couponCode, string customerClass)
{
...
}
var query = breeze.EntityQuery.from("OrderItemsWithCouponCode").withParameters({ couponCode:"XYZ", customerClass:"Gold"});

Server-Driven Queries (group by, sum, ...)
public IQueryable<object> TopCustomers()
{
var query = (from o in _ContextProvider.Context.Orders
group o by o.CustomerId into g
select new { Customer = g.FirstOrDefault().Customer.LastName, Total = g.Sum(o => o.ItemsTotal) }).OrderByDescending(a => a.Total);
return query;
}
... from("TopCustomers").take(10);

... requeryProducts.push(product)
... fromEntities(requeryProducts); //or by id that replaces the cache, you can also put a sigle entity

breeze.EntityQuery.from("Products").where(...)
... em.executeQueryLocally(query); //synchronous
query.using(breeze.FetchStrategy.FromLocalCache); //async with promise

EntityAspect: contains metadata and state information about the entity

  • breeze.NamingConvention.camelCase.setAsDefault(); //type and collections are pascal case, properties or camelcase

EntityManager.createEntity: creates instance and add to cache
newCustomer = function() {
var cust - em.createEntity("Customer", { id: breeze.core.getUuid() });
return cust;
}

EntityType.createEntity: reference from meta, entity from meta, create, add to cache
newCustomer = function() {
var metadataStore = em.metadataStore;
var custType = metadataStore.getEntityType("Customer");
var cust = custType.createEntity({ id: breeze.core.getUuid() });
em.addEntity(cust);
return cust;
}

var pendingStatus = em.executeQueryLocally(breeze.EntityQuery.from("OrderStatuses").where("name", "equals", "Pending"))[0];
order.orderStatusId(pendingStatus.id());
return order;

addToOrder = function(product) {
if (!Order()) order(zzaDataService.createNewOrder(customer()));
var orderItem = order().entityAspect.entityManager.createEntity("OrderItem");
orderItem.order(order());
orderItem.product(product);
orderItem.productSizeId(1);
orderItems.push(orderItem);
}

editingCustomer().entityAspect.rejectChanges();
em.rejectChanges();

//after accepting changes you will not be able to persist them !
... entityAspect.acceptChanges(); //should only be used when mocking data

customer.entityAspect.setDeleted();
customers.remove(customer);

Named Saves
zzaDataService.saveCustomers().fail(...)
...
var customers = em.getChanges("Customer");
return em.saveChanges(customers);
...
var saveOptions = new breeze.SaveOptions({ resourceName:"SaveOrder" });
var orders = [order];
return em.saveChanges(orders, saveOptions);
...
[HttpPost]
public SaveResult SaveOrder(JObject saveBundle)
{
return _ContextProvider.SaveChanges(saveBundle);
}

Working with Entities on the client

Entity States:

  • Unchanged - after being loaded by a query or after successful saveChanges
  • Modified - previously unchanged entity has a property change
  • Added - entity has been created and added to the cache, but not saved to back end
  • Deleted - previously unchanged entity is marked for deletion
  • Detached

Newly created entity that has not been added to the cache
Added entity that gets marked for deletion
Deleted entity after saveChanges is complete
Entities that were in the cache when EntityManager.clear is called
EntityManager.detachEntity called
EntityAspect.entityState
createEntity, setDeleted, rejectChanges, acceptChanges
EntityAspect.setModified, EntityAspect.setUnchanged

Extending Entities: Modify the EntityType in the Breeze metadataStore
Saved when exported, but not persisted to the server.

addEntityExtensions = function () {
var store = em.metadataStore;
store.registerEntityTypeCtor("Customer", Customer)
}
var Customer = function() {
this.firstName = ko.observable(''); //solves race condition
this.lastName = ko.observable('');
this.fullName = ko.computed(function() {
return this.firstName() + " " + this.lastName();
}, this);
}
OR
Customer.prototype.getFullName = function() {
return this.firstName() + " " + this.lastName();
}

Handling Entity Property Changes

  • Breeze raises its own (observable agnostic) property change events
  • EntityAspect.propertyChanged.subscribe returns a subscription token
  • Avoid memory leaks by EntityAspect.propertyChanged.unsubscribe(token)
tokens = {};
var token = addingCustomer().entityAspect.propertyChanged.subscribe(function()arg {
logger.info("Customer:" + arg.entity.fullName() + "property " + arg.propertyName + "changed value" + arg.oldValue + "into" + arg.newValue);
})
tokens[addingCustomer().id] = token;
... unsubscribe(tokens[customers()[i].id]) ...

EntityManager.entityChanged.subscribe (eg. undo)
-> entityAction, entity, args

Exporting and importing cached entities
Using an entityChangedHandler to make sure changes are saved locally for when user would close browser before saving
var bundle = em.exportEntities(em.getChanges());
window.localStorage.setItem("magickey", bundle);
...
var bundle = window.localStorage.getItem("magickey");
if (bundle) em.importEntities(bundle);
...
clear local storage on save

Validation

  • Returns HTTP 403 Forbidden status code when validation errors
  • Collection of ValidationError objects per entity (propertyName, property, errorMessage, context, isServerError, key, validator)
  • Automatically adds type, required and length validation
  • Configurable - EntityManager.validationProperties
saveError = function (error) {
if (error.entityErrors) {
showValidationErrors(error.entityErrors);
}
else {
logger.error(error.message, "Error saving data");
}
}
showValidationErrors = function (errors) {
var errorMessage = "";
errors.map(function (e) {
if (errorMessage.length > 0) errorMessage += ", ";
errorMessage += e.errorMessage;
});
logger.error(errorMessage, "Validation Errors");
}

Data Annotations (System.ComponentModel.DataAnnotations)
[Required], [StringLength(50)], Range, RegularExpresssion, Compare
[DataType(DataType.Currency)], Date, Time, DateTime, PostalCode, ...
Phone, [EmailAddress], CreditCard, Url

function initValidation(em) {
var store = em.metadataStore;
var userType = store.getEntityType("User");
var phoneProp = userType.getProperty("phone");
phoneProp.validators.push(breeze.Validator.phone());
}

var userKeyValidator = breeze.Validator.makeRegExpValidator(
"userKeyVal",
/^[A-Z] ... regex ... $/,
"%displayName% '%value%' is not a valid GUID");
);

if (validateUser(editingUser()) { ... saveChanges ... }
validateUser = function(user) {
if (!user.entityAspect.validateEnitity()) {
var errors = user.entityAspect.getValidationErrors();
showValidationErrors(errors);
return false;
}
return true;
}


function isValidScoreRange(value, context) {
return value > context.minValue && value <= context.maxValue;
}
var scoreRangeValidator = new breeze.Validator("scoreRangeValidator", isValidScoreRange, { messageTemplate: '...' });
userType.getProperty("score").validators.push(scoreRangeValidator);
function rangeValidatorFactory(context) {
return new breeze.Validator("rangeValidator", isValidRange,
{
minValue: context.minValue,
maxValue: context.maxValue,
messageTemplate: '... %minValue% ... %maxValue% ...'
OR messageTemplate: breeze.core.formatString("'... %1 ... %2'", context.minValue, context.maxValue)
}
);
}
userType.getProperty("score").validators.push(rangeValidatorFactory({ minValue:1, maxValue:100 }));

//register to allow export and imports
breeze.Validator.register(userKeyValidator);
breeze.validator.registerFactory(rangeValidatorFactory, "rangeValidator");


app.em.validationErrorChanged.subscribe(function(args) {
if (args.added) ...
}


editSubscriptionToken = editingUser().entityAspect.validationErrorsChanged.subscribe(...);
... unsubscribe ...


Customer Server Validation

  • Inherit from ValidationAttribute
  • CustomValidatorAttribute
  • Custom business logic (EntityErrorsException from BreezeController BeginSaveEntity)
  • [CustomValidation(typeof(UserValidationRules), "ValidateEmail")]
public static class UserValidationRules
{
public static ValidationResult ValidateEmail(string value, ValidationContext context)
{
if (!value.EndsWith("...") return new ValidationResult("...");
return ValidationResult.Success;
}
}

private bool BeforeSaveEntity(EntityInfo entityInfo)
{
User user = entityInfo.Entity as User;
if (user != null)
{
if (!user.CreditCard.StartsWith("5"))
{
throw new EntityErrorsException(new List<EntityErrors>)
{
new EntityError
{
EntityTypeName = user.GetType().ToString(),
ErrorMessage = "We only accept Mastercard",
PropertyName = "CreditCard",
ErrorName = "CreditCardValidationError",
KeyValues = new object[] { user.Id }
}
}
}
}
return true;
}

SignalR - Notes 

Categories: .Net Javascript

Real-time Web & Push Services with Hubs
by Christian Weyer (christian.weyer@thinktecture.com)
Developers of SignalR on https://jabbr.net

Push Services pattern (models a service that) 

  • accepts incoming connections from callers
  • is able to push data down to callers
  • abstracts from communication nitty-gritty details
  • consumers send to push service
  • push service notifies consumers
  • external events (Db, Message Queue, ...) trigger push service

HTTP is the protocol

  • Periodic Polling
  • Long Polling (http streaming/comet) -> server only responds when there is data
  • Forever Frame (connection within an iframe)
  • Server-Sent Events (SSE)
  • Web Sockets (only Windows 8/Server 2012, network considerations/blockings)

Hubs

  • public methods are callable from the outside
  • send messages to clients by invoking client-side methods
  • [HubName("chat")]
  • Context holds connection- and request-specific information
  • ConnectionId, Request, Headers, RequestCookies, QueryString, User
  • First register the hubs and then add your other routes.
  • Target method with parameters is dynamically 'injected' into code path, serialized, and embedded into protocol response
  • Groups are not persisted on the server
  • We need to keep track of what connections are in chat groups
  • No automatic group count 
Clients.Caller.newMessage(message);
Clients.Client(Context.ConnectionId).newMessage(message);
Clients.All.newMessage(message);
Clients.Others.newMessage(message);
Clients.AllExcept(Context.ConnectionId).newMessage(message);

Groups.Add(Context.ConnectionId, room);
Clients.Group(room).newMessage(msg);

public override Task OnConnected() {}
public override Task OnDisconnected() {}
public override Task OnReconnected() {}

// Sending data from outside a hub, retrieve hub context via dependency resolver
private void SendMonitorData(string eventType, string connection)
{
var context = GlobalHost.ConnectionManager.GetHubContext<MonitorHub>();
context.Clients.All.newEvent(eventType, connection);
}

Clients

  • .NET4.0+, WinRT, Windows Phone 8, Silverlight 5, jQuery, C++, iOS native, iOS via Mono, Android via Mono
  • /signalr/hubs is the dynamic proxy contract
  • use signalr.exe ghp /url:http://localhost:31373 tool to create a static proxy

Javascript with proxy

 $.connection.hub.logging = true;
chat = $.connection.chat
$.connection.hub.start({ transport:'longPolling' });
$.connection.hub.connectionSlow = onConnectionSlow;

Javascript without proxy

var connection = $.hubConnection();
var proxy = connection.createHubProxy('chat');
proxy.on('newMessage', onNewMessage);
connection.start();
proxy.invoke('sendMessage', $('#message').val());

.Net

var hubConnection = new HubConnection("http://localhost/ps");
var chat = hubConnection.CreateHubProxy("chat");
chat.On<string>("newMessage", msg => messages.Invoke(new Action(() => messages.Items.Add(msg));
hubConnection.Start().Wait();
// Opened, Closed, Error, Received, Reconnected, Reconnecting, StateChanged

Hosting

OWIN (Open Web Server Interface for .Net) - http://owin.org
Katana project - http://katanaproject.codeplex.com

  • Application delegate (AppFunc) var AppFunc = Func<IDictionary<string, object>, Task>;
  • Environment dictionary
  • ASP.Net hosting sits on top of OWIN
  • Self-hosting
  • Microsoft.Owin.Hosting aka Katana
  • Microsoft.Owin.Host.HttpListener
// process user needs http.sys permissions on the url namespace
public void Configuration(IAppBuilder app)
{
var a = Assembly.LoadFrom("Services.dll");
app.MapHubs(new HubConfiguration { EnableCrossDomain = true });
}

using(WebApplication.Start<Startup>("http://localhost:6789")
{
Console.WriteLine("Hubs running...");
Console.ReadLine();
}

// jQuery client needs to explicitly set connection url
$.connection.hub.url = "http://localhost:6789/signalr";

Azure

  • Which server instance handles the client request?
  • Which instance pushes?

Other aspects

  • Use SignalR message bus
  • SignalR extensible architecture
  • Persistent connections
  • Scale-Out
  • WebApi Integration
  • Security
Page 1 of 4 1 2 3 4 > >>