Fotorama: how should I initialize Fotorama that is created dynamically? - angularjs

I wish to use Fotorama in my project. But my project uses AngularJS to load the partial view which contains
<div class="fotorama">
<img src="1.jpg"><img src="2.jpg"><img src="3.jpg">
</div>
But that cannot initialize fotorama since the html is loaded via AJAX.
How should I load Fotorama under this situation?
The problem is I think similar to handling event on element created dynamically. For instance,
<div class="myImg">Some Img</div>
And you write,
$('.myImg').on('click', function() {
alert('Clicked');
});
The alert won't fire if the div is created dynamically. But the following would work:
$(document).on('click', '.myImg', function() {
alert('Clicked');
});
I think the same case is true for fotorama. So how should I do similar thing for the case of Fotorama?
Thank you very much.

You may check official documentation: http://fotorama.io/customize/api/
Or use one of angular wrappers: https://github.com/tamtakoe/ap.fotorama
Probably, you have to parse images from HTML chunk, or redesign it all (sending HTML by AJAX isn't good approach) to send JSON. Something like this:
$('.fotorama').fotorama({
data: [
{img: '1.jpg', thumb: '1-thumb.jpg'},
{img: '2.jpg', thumb: '2-thumb.jpg'}
]
});
Or like this (will modify existing fotorama instance):
fotorama.load([
{img: '1.jpg', thumb: '1-thumb.jpg'},
{img: '2.jpg', thumb: '2-thumb.jpg'}
]);
PS
If you going to create DOM element dynamically, you should add event listeners after it been added to document. for example
$.get({/* url, etc */})
.success(plainHtml) {
$('el').append(plainHtml)
$('selector').on('click', onClickCallback)
})
Also, you can manually initialize fotorama on the DOM element: $('.fotorama').fotorama(). In onSuccess callback, of course. Actually, you shouldn't use jQuery in AngularJs projects.

Related

Use the same template on different scopes

I have a ui-grid that allows users to select a row:
angular.module("app").component("mycomponent", {
templateUrl: "View.html",
controllerAs: "m",
controller: [Interactor_Controller]
})
function Interactor_Controller() {
var m = this
m.gridOptions = {enableRowSelection: true, enableRowHeaderSelection: false}
m.gridOptions.columnDefs = [
{field: "id"},
{field: "name"}
]
m.gridOptions.multiSelect = false
m.gridOptions.onRegisterApi = function(gridApi){
m.infoGridApi = gridApi
gridApi.selection.on.rowSelectionChanged(null, function(row){
m.infoGridSelectedRow = row.entity
})
}
$http
.get('lottastuff')
.success(response => {
m.gridOptions.data = response
m.gridApi.selection.selectRow(m.gridOptions.data[0]);
m.gridSelectedRow = m.gridApi.selection.getSelectedRows()
})
}
The template would be just a ui-grid invocation:
<div class="gridStyle" ui-grid="m.gridOptions" ui-grid-selection></div>
I'd like to be able to do something like this:
<div class="container-fluid">
<div class="xs-col-12">
<myReusableGrid>
</div>
</div>
(Where the scope is implied because the component is using that page as it's template).
This works when it's built inside a controller, but I need to
Reuse in multiple views
Get data from the controller
Send the selected row to the controller and the view it's being inserted into.
What should I use to accomplish this? Directive? Factory? Template? Something else? I'm not exactly sure how to do most of that, but I want to make sure I'm on the right track before I spend time figuring it out (time is essential).
How do I send data from the controller?
How do I send data to the controller?
I recommend you use a Component.
In AngularJS, a Component is a special kind of directive that uses a
simpler configuration which is suitable for a component-based
application structure.
This makes it easier to write an app in a way that's similar to using
Web Components or using the new Angular's style of application
architecture.
Source: https://docs.angularjs.org/guide/component
I really like components because is write a controller for a specific part of your view, instead of a state.
Also, take the components path in angularjs, will make easier to you learn Angular (aka angular2).
This tutorial help me a lot: https://tests4geeks.com/build-angular-1-5-component-angularjs-tutorial/

Backbone events are not working

For some reason I don't know why my event in a Backbone View doesn't work.
I tried to Google for some answer but I didn't find anything that would help me.
Basically, my code is this:
Backbone:
var ViniView = Backbone.View.extend({
el: $('.container'),
events: {
"click .clickme" : "render"
},
render: function() {
alert("please, work");
}
});
new ViniView;
HTML
<div class="container">
<button class="clickme">
test
</button>
</div>
Your example works fine for me in this fiddle.
As explunit noted, though, your el should reference an element and should not be a jQuery object. $el takes care of that. According to the docs:
All views have a DOM element at all times (the el property), whether they've already been inserted into the page or not.
Check that you're correctly loading the Jquery, Underscore and Backbone scripts (in that order). Also make sure you're script is being executed once the page is ready and not, say, before your DOM has finished loading (causing your view to not attach to anything).

The angular.js best practices way to query the current state of the DOM

I've started working with angular js and have a problem that requires getting the current state of the DOM inside of my controller. Basically I'm building a text editor inside of an contenteditable div. Revisions to the text in the div can come from an external service(long polling pushes from the server) as well as the user actually typing in the field. Right now the revisions from the server are manipulating my angular model, which then updates the view through an ng-bind-html-unsafe directive. The only problem with this is that this blows away the users current cursor position and text selection.
I've figured out a way around the problem, but it requires directly manipulating dom elements in my controller, which seems to be discouraged in angular. I'm looking for either validation of my current method, or reccomendations on something more "angulary".
Basically what I've done is added two events to my model, "contentChanging" and "contentChanged". The first is fired right before I update the model, the second right after. In my controller I subscribe to these events like this.
//dmp is google's diff_match_patch library
//rangy is a selection management library http://code.google.com/p/rangy/wiki/SelectionSaveRestoreModule
var selectionPatch;
var selection;
scope.model.on("contentChanging", function() {
var currentText = $("#doc").html();
selection = rangy.saveSelection();
var textWithSelection = $("#doc").html();
selectionPatch = dmp.patch_make(currentText, textWithSelection);
});
scope.model.on("contentChanged", function() {
scope.$apply();
var textAfterEdit = $("#doc").html();
$("#doc").html(dmp.patch_apply(selectionPatch, textAfterEdit)[0]);
rangy.restoreSelection(selection);
});
So basically, when the content is changing I grab the current html of the editable area. Then I use the rangy plugin which injects hidden dom elements into the document to mark the users current position and selection. I take the html without the hidden markers and the html with the markers and I make a patch using google's diff_match_patch library(dmp).
Once the content is changed, I invoke scope.$apply() to update the view. Then I get the new text from the view and apply the patch from earlier, which will add the hidden markers back to the html. Finally I use range to restore the selection.
The part I don't like is how I use jquery to get the current html from the view to build and apply my patches. It's going to make unit testing a little tricky and it just doesn't feel right. But given how the rangy library works, I can't think of another way to do it.
Here's a simple example of how you would start:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
<script type="text/javascript">
function Ctrl($scope) {
$scope.myText = "Here's some text";
}
angular.module("myApp", []).directive('texteditor', function() {
return {
restrict: 'E',
replace: true,
template: '<textarea></textarea>',
scope: {
text: '=' // link the directives scopes `text` property
// to the expression inside the text attribute
},
link: function($scope, elem, attrs) {
elem.val($scope.text);
elem.bind('input', function() {
// When the user inputs text, Angular won't know about
// it since we're not using ng-model so we need to call
// $scope.$apply() to tell Angular run a digest cycle
$scope.$apply(function() {
$scope.text = elem.val();
});
});
}
};
});
</script>
</head>
<body>
<div ng-controller="Ctrl">
<texteditor text="myText"></texteditor>
<p>myText = {{myText}}</p>
</div>
</body>
</html>
It's just binding to a textarea, so you would replace that with your real text editor. The key is to listen to changes on the text in your text editor, and update the value on your scope so that the outside world know that the user changed the text inside the text editor.

Backbone, not "this.el" wrapping

I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.

underscore template is removed from the DOM so can't be re-used

I'm trying to use a simple inline underscore.js template with backbone, and pulling the template from a <script> tag via jQuery's html() method.
The page is supposed to render multiple Recipe callouts for each returned by the Recipe Model. It works on the first thumbnail, but on the second, it seems the <script> tag has been removed from the DOM, so underscore fails to render it and bails with str is null on line 913
For the template, I have
<script type="text/html" id="user-recipe-rated-template">
<div class="user-recipe-rated">
<a class="thumb" href="<%= href %>"></a>
<p><%= title %></p>
</div>
</script>
And the backbone view looks like:
var UserRecipeView = Backbone.View.extend({
initialize : function() {
this.template = _.template($("#user-recipe-rated-template").html());
},
render : function()
{
this.el = this.template({
title: this.model.get("Title"),
href: '#'+this.model.get('UserContentId'),
image_src: this.model.get('ThumbnailSrc')
});
return this;
}
});
So, what I am seeing is that $("#user-recipe-rated-template") exists on the first thumbnail and all is well. On the second, it returns an empty array and underscore can't proceed.
I've trying memoizing the string of html and such, and that might work, but it seems there should be a cleaner way of doing this. What am I doing wrong?
(I'd like to use templates inlined as opposed to external JST for now to keep things simple - it seems nicer to have the template embedded in the page near where it will be loaded when the data comes in)

Resources