Why are Leaflet's Map Methods Missing? - leaflet

I have created a Leaflet map by calling var map = L.map('leaflet', options); but that just makes an object full of underscore-prefixed private values:
Object.keys(map) === ["options", "_container", "_leaflet_id", "_containerId", `"_fadeAnimated", "_panes", "_paneRenderers", "_mapPane", "_controlCorners", "_controlContainer", "_onResize", "_targets", "_events", "_zoom", "_loaded", "_lastCenter", "_size", "_sizeChanged", "_pixelOrigin", "_firingCount", "_handlers", "_layers", "_zoomBoundLayers", "_initHooksCalled", "dragging", "doubleClickZoom", "scrollWheelZoom", "touchZoom", "boxZoom", "keyboard", "_zoomAnimated", "_proxy", "_layersMaxZoom", "_layersMinZoom"]`
The map still shows up. There is no console error. But I can't call things like map.getCenter() or map.getContainer() because those methods do not exist on map. Why are those methods not on the map?

Remember that Object.keys returns only enumerable own properties of an object.
By definition, methods are supposed to be inherited from a class, and not be own properties.
In JavaScript (ES5), there is no formal "class", so it translates to "methods be inherited from the prototype". But still not own properties.
Leaflet does a good job in that it respects this practice.
For instance, instead of analyzing Object.keys(map), directly look into map (in modern browsers like Firefox, doing console.log(map) will give you the ability to expand the object) and search for __proto__ (could be the last item): this is the link to the prototype, which correctly lists the methods like getCenter and getContainer.
Demo: https://jsfiddle.net/y63u5utf/7/

Related

v2.ODataModel: which API is more preferred? "bindElement" or "read"?

I set the view.setModel(model), get the model for the view, and request for model.read("/entitySet('10000')").
The model is then filled up with /entitySet('10000')/properties.
But it is difficult to assign them to view fields, as now in the view, <Text text="{property}"> doesn't work. It has to be <Text text="{/entitySet('10000')/property}">.
On the other hand, if I set view's context binding to "/entitySet('10000')", then <Text text="{property}"> would start working.
Which one is the preferred method? When to use .read?
I almost never use .read if I want to use the results from an OData call directly in a binding context. The only time I use .read is if I want to manipulate the results before doing anything with them.
Look at this example from the sdk for instance: https://ui5.sap.com/#/entity/sap.ui.table.Table/sample/sap.ui.table.sample.OData
Syntax on this kind of binding is similar to read but with a few differences in events, and a few different types of methods depending on what you want to bind. Binding to a view for instance uses bindElement:
this.getView().bindElement("/entitySet('1000')");
After this, fields on that particular entity can be accessed as <Text text="{property}" />.
Here's an example from one of my current apps with events and some other call parameters:
this.getView().bindElement({
path: `/Orders('${currentOrderNumber}')`,
parameters: {
expand: 'Texts'
},
events: {
dataRequested: _ => this.getView().setBusy(true),
dataReceived: data => {
if (!this.getView().getBindingContext()) {
// navigate to `Not Found` view
}
},
change: _ => this.getView().setBusy(false)
}
});
For a table, it's slightly different, since it depends on the aggregation you wish to bind, such as
oTable.bindRows({
path: "properties"
});
Which is the same as:
<Table rows="{properties}" />
It's always important to be more expressive. Use the API that is specifically designed to do that one task.
Comparing the two variants:
myModel.read(sPath) with text="{/path/property}"
myControl.bindElement(sPath) with text="{property}"
I'd be perplexed about the 1st call whereas in the 2nd call, I'd know exactly what you want to achieve (You want to bind element. Alternatively, bindObject can be also used).
The same applies to the framework. Since you're telling exactly what you want to achieve, the framework can improve its behavior based on your intent. E.g.: in (route)PatternMatched handler when the user navigates to the same page, .bindElement with the same path won't trigger another request since the model already stored the entity from the previous call. It can show the result immediately.
With .read, however, the framework doesn't know what you want to achieve, so it sends the request right away regardless of the application state.
Additionally, the 1st variant is anything but future-proof. It relies on the cached results. It's almost a side-effect that it works at all. The problem is that there is no guarantee that this behavior will continue to work in later versions. Also there won't be read method in V4 ODataModel.
TL;DR
v2.ODataModel#read
Does not create context from the response. Repeating .read("<same path>") always sends a new request.
Less expressive. Encourages app developers to work with a client-side model (e.g. JSONModel).
Application loses context awareness, increasing TCO, less future-proof.
bindElement or bindObject
Creates context from the response and stores it internally so that the same request can return the data immediately.
Clearly expresses the intent; application as well as framework can work with the existing APIs.
More future-proof: v4.ODataModel does not support manual read. Imagine you've built your applications with the v2ODataModel.read-jsonModel.setData approach, and you need to migrate to v4. Have fun. :)
I honestly think that v2.ODataModel#read should have never become a public method. I wouldn't encourage anyone to use .read except of when reading the $count value manually.
If the entity values need to be formatted, there are formatters and binding types out of the box which are also easy to extend.
If the application needs to restructure the response body, usually it's a sign of a poor design of the data model or the service not conforming to the OData spec.
I almost agree with Jorg, but not entirely:
It really depends on what are you trying to achieve. If looking to display data from backend then the easiest way to go is with this.getView().bindElement()
but if you are in need to manipulate data before displaying (like formatting text, displaying images from base64 strings, etc) OR if you would like to create new entity using some of existing data, OR update the existing data - the this.getModel(sName).read() is the way to go - as you can set read entity with all its deep entities to JSONModel in successCallback and manipulate it from the localModel.
If using localModel the dataBinding is pretty much the same in the view - except you have to additionally give model name to take data from. So for example, if in successCallback of your Model.read() you set your data to Model named "localModel":
this.getModel().read(sObjectPath, {
urlParameters: {
$expand: ""
},
success: function (oData) {
// "localModel" is name you gave in onInit function to your new JSONMOdel, when setting it to View e.g. this.getView().setModel(oJSONMOdel, "localModel")
this.getModel("localModel").setData(oData);
}
})
then in XML view you would indicate that
<Text text="{localModel>/mainPropertyName}"/>
// for displaying deep entities as List items or in Table
<List items="{localModel>/deepEntityName}">
<StandardListItem title="{localModel>propertyNamefromDeepEntity}" />
</List>
From my experience working with more complex apps that Read-Only - I always use Model.read().

Getting properties from AEM multifieldpanel dialog stops working when a second entry is added

I have created an AEM Dialog which prompts the user for a set of links and labels.
These links and labels are stored in a jcr node and are used to generate a menu.
To avoid having to create a custom xtype, I am using the acs-commons multifieldpanel solution, which enables me to nest children under the fieldConfig node.
This works great with only 1 Label/Link pair, but when I add a second one - the property cannot be fetched anymore, since instead of a String, it returns the String hashcode.
The property generated by the multifieldpanel in the jcr node is of type String and is filled correctly when inspecting in CRXDE. The problem occurs when I try to fetch the value from within a Sightly HTML file.
Code
Dialog:
Definitions.js:
"use strict";
use(function () {
var CONST = {
PROP_URLS: "definitions",
};
var json = granite.resource.properties[CONST.PROP_URLS];
log.error(json);
return {
urls: json
};
});
Log output
1 element in multifieldpanel
jcr node variable content
definitions: {"listText": "facebook", "listPath": "/content/en"}
log output
{"linkText":"facebook","linkPath":"/content/en"}
Multiple elements in multifieldpanel
jcr node variable content
definitions: {"listText": "facebook", "listPath": "/content/en"},{"listText": "google", "listPath": "/content/en"}
log output
[Ljava.lang.String;#7b086b97
Conclusion
Once the multifieldpanel has multiple components and stores it, when accessing the property the node returns the String hashcode instead of the value of the property.
A colleague has pointed out that I should use the MultiFieldPanelFunctions class to access the properties, but we are using HTML+Sightly+js and are trying to avoid .jsp files at all cost. In JavaScript, this function is not available. Does anyone have any idea how to solve this issue?
That is because, when there is a single item in the multifield, it returns a String, where as it returns a String[] when there is more than a single item configured.
Use the following syntax to read the property as a String array always.
var json = granite.resource.properties[CONST.PROP_URLS] || [];
Additionally, you can also use TypeHints to make sure your dialog saves the value as String[] always, be it single item or multiple items that is configured.
Don't forget that the use() in JS is compiled into Java Byte code and if you are reading Java "primitives", make sure you convert them to JS types. It's part of the Rhino subtleties.
On another note, I tend to not use the granite.* because they are not documented no where, I use the Sightly global objects instead https://docs.adobe.com/content/docs/en/aem/6-0/develop/sightly/global-objects.html
To access properties, I use properties.get("key")
Hope this help.

Navigation Property Filter

My question is this: How can you implement a default server-side "filter" for a navigation property?
In our application we seldom actually delete anything from the database. Instead, we implement "soft deletes" where each table has a Deleted bit column. If this column is true the record has been "deleted". If it is false, it has not.
This allows us to easily "undelete" records accidentally deleted by the client.
Our current ASP.NET Web API returns only "undeleted" records by default, unless a deleted argument is sent as true from the client. The idea is that the consumer of the service doesn't have to worry about specifying that they only want undeleted items.
Implementing this same functionality in Breeze is quite simple, at least for base entities. For example, here would be the implementation of the classic Todo's example, adding a "Deleted" bit field:
// Note: Will show only undeleted items by default unless you explicitly pass deleted = true.
[HttpGet]
public IQueryable<BreezeSampleTodoItem> Todos(bool deleted = false) {
return _contextProvider.Context.Todos.Where(td => td.Deleted == deleted);
}
On the client, all we need to do is...
var query = breeze.EntityQuery.from("Todos");
...to get all undeleted Todos, or...
var query = breeze.EntityQuery.from("Todos").withParameters({deleted: true})
...to get all deleted Todos.
But let's say that a BreezeSampleTodoItem has a child collection for the tools that are needed to complete that Todo. We'll call this "Tools". Tools also implements soft deletes. When we perform a query that uses expand to get a Todo with its Tools, it will return all Tools - "deleted" or not.
But how can I filter out these records by default when Todo.Tools is expanded?
It has occurred to me to have separate Web API methods for each item that may need expanded, for example:
[HttpGet]
public IQueryable<Todo> TodoAndTools(bool deletedTodos = false, bool deletedTools = false)
{
return // ...Code to get filtered Todos with filtered Tools
}
I found some example code of how to do this in another SO post, but it requires hand-coding each property of Todo. The code from the above-mentioned post also returns a List, not an IQueryable. Furthermore this requires methods to be added for every possible expansion which isn't cool.
Essentially what I'm looking for is some way to define a piece of code that gets called whenever Todos is queried, and another for whenever Tools is queried - preferably being able to pass an argument that defines if it should return Deleted items. This could be anywhere on the server-side stack - be it in the Web API method, itself, or maybe part of Entity Framework (note that filtering Include extensions is not supported in EF.)
Breeze cannot do exactly what you are asking for right now, although we have discussed the idea of allowing the filtering of "expands", but we really need more feedback as to whether the community would find this useful. Please add this to the breeze User Voice and vote for it. We take these suggestions very seriously.
Moreover, as you point out, EF does not support this.
But... what you can do is use a projection instead of an expand to do something very similar:
public IQueryable<Object> TodoAndTools(bool deleted = false
,bool deletedTools = false) {
var baseQuery = _contextProvider.Context.Todos.Where(td => td.Deleted == deleted);
return baseQuery.Select(t => new {
Todo: t,
Tools: t.Tools.Where( tool => tool.Deleted = deletedTools);
});
}
Several things to note here:
1) We are returning an IQueryable of Object instead of IQueryable of ToDo
2) Breeze will inspect the returned payload and automatically create breeze entities for any 'entityTypes' returned (even within a projection). So the result of this query will be an array of javascript objects each with two properties; 'ToDo' and 'Tools' where Tools is an array of 'Tool' entities. The nice thing is that both ToDo and Tool entities returned within the projection will be 'full' breeze entities.
3) You can still pass client side filters based on the projected property names. i.e.
var query = EntityQuery.from("TodoAndTools")
.where("Todo.Description", "startsWith", "A")
.using(em);
4) EF does support this.

Appending Child Models to a Parent Model in Ember.js

I have a parent model that has many child models, for example:
App.Blog = Ember.Object.extend({})
App.Comment = Ember.Object.extend({})
Then I have a view for the Blog:
window.App.BlogView = Ember.View.extend
templateName: 'app_templates_blog'
Now, when I initially retrieve a Blog via my REST API, it contains the first 10 Comments or so. Those are instantiated like this:
window.App.Blog.reopenClass
create: (obj) ->
Object.tap #_super(obj), (result) =>
if result.comments
result.comments = result.comments.map (p) => App.Comment.create(p)
I display by blog by calling BlogView.set('blog', blogInstance) and everything is displaying perfectly.
However!
I am implementing an infinite scroll, so when the end user gets to the bottom of the comments, I want to load more. I do this via REST, but I can't for the life of me get them to display by appending them.
This is what my morePosts() method looks like in BlogView:
moreComments: () ->
blog = #get('blog')
jQuery.getJSON "/blogs/#{blog.get('id')}/comments", (result) =>
comments = blog.get('comments')
result.each (c) => comments.push(App.Comment.create(c))
blog.set('comments', comments)
this.set('blog', blog)
However, this never seems to add the new comments. What am I doing wrong here?
To properly support Ember's bindings and observers, you need to use KVO aware getters and setters. Just as you use get and set for standard properties, you also have to use special methods for Arrays. In this case, you'd use pushObject. You can see a full list of the functions implemented here: https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/mixins/mutable_array.js.

Using Google Maps v3 DrawingManager from GWT

I am working on a project that uses GWT 2.4 and gwt-maps.jar to create a MapWidget and place it in a panel with various controls. This all works fine.
I would like to give my users the ability to draw a polyline on the map and use the getLength method in Polyline to determine the length of the drawn polyline. I have done this before in ActionScript and it was a bit of a pain in the neck (the rubber-banding between the last clicked point and the mouse in particular) and I was hoping not to have to do that again.
The drawing manager looks like it might be a good fit (for the drawing part at least) but it is in v3 of the API and the gwt-maps.jar code only is at v2. So I thought I might write some JavaScript and call out from GWT using JSNI, something along the lines of (in wibble.html - my top-level HTML file):
var dM = new google.maps.drawing.DrawingManager( ...
function showDM(map) {
dM.setMap(map);
dM.setOptions({
drawingControl: true
});
And then (in Wobble.java):
private MapWidget map = new MapWidget( ...
private native void showIt(final MapWidget map) /*-{
$wnd.showDM(map);
}-*/;
I have tried passing the MapWidget and its peer but in both cases I get an invalid value error when calling setMap.
Has anybody tried (and succeeded) in doing this or am I barkig up the wrong tree?
Thanks,
SO
First of all, I only have experience with GWT and GWT-JS communication. Not Google APIs.
Now:
Looks like you are passing a GWT object (compiled javscript object) to DrawingManager. The problem is the DrawingManager API receives "nice javascript objects" (not objects with obfuscated methods).
If you want to pass an HTML Element is ok (but then, you must pass widget.getElement() that really is a <div> object (by example).
Solution
Indeed GMaps API docs says you must pass a Map object from the GMap API. You create that map with an element that will be the canvas.
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
If you want to use your MapWidget as the map canvas then you can use its HTML Element.
In GWT:
private native void showIt(final MapWidget map) /*-{
$wnd.showDM(map.getElement()); // use mapwidget's element as canvas
}-*/;
In javascript:
function showDM(canvasToUse) {
// TODO: define myOptions :)
var map = new google.maps.Map(canvasToUse, myOptions);
dM.setMap(map);
dM.setOptions({
drawingControl: true
});
Disclaimer
It's only based on my experience with GWT and JSNI. I didn't try it nor have experience with GMaps or DrawingManager. You should check what I say and tell me if I had luck :)
Hope it helps!