Calling holder.js in Meteor - meteor

I'm new to Meteor and trying to get holder.js to work in the framework. It works on refresh, but when moving from one route to another, it breaks.
The documentation just says "Because Meteor includes scripts at the top of the document by default, the DOM may not be fully available when Holder is called. For this reason, place Holder-related code in a "DOM ready" event listener."
I assume I need a Template.foo.onRendered callback, but unsure how to format it. Here's the HTML:
<img class="holder" src="holder.js/120x120">
And here's the callback I've added in a .js file:
Template.contactSingle.onRendered(function() {
this.$('.holder').Holder.run();
});
Again, the holder.js images appear on refresh, but I can't get them to render when going from one page to another. I'm using FlowRouter for routing.
I'm sure it's something simple. Any help is greatly appreciated!

Change your code from:
Template.contactSingle.onRendered(function() {
this.$('.holder').Holder.run();
});
to:
Template.contactSingle.onRendered(function() {
Holder.run({images: document.querySelectorAll('.holder')});
});
Obviously you do not want to do the costly document.querySelectorAll('.holder'). If you can reduce that to you template using a class from its wrapper.
For example:
Template:
<template name="singlePost">
<div class="single-post">
<h2>This is the singlePost area.</h2>
<img class='holder' src="holder.js/300x200">
</div>
</template>
and onRendered
Template.singlePost.onRendered(function() {
Holder.run({
images: document.querySelectorAll('.single-post .holder')
});
});

Related

Why is mixing Razor Pages and VueJs a bad thing?

I'm trying to set up a .NET core project using Razor Pages and include vueJs inside the razor page for all my logic.
Something like this:
#{
ViewData["Title"] = "VueJs With Razor";
}
<h2>#ViewData["Title"].</h2>
<div id="app">
<span>{{ message }}</span>
</div>
<script>
new Vue({
el: '#app',
data: {
message : 'Hello vue.js'
}
})
</script>
I have read that mixing Vue and Razor pages is a bad practice, and one should use Razor OR Vue.
Why is this?
Mixing VueJs and Razor Pages is not necessarily a bad thing, it can be great!
I use Vue with razor for non SPA pages and the two work well together. I choose to use Vue by loading it via a script tag from a CDN and and I do not leverage the use of WebPack for transpiling, I simply write my code in (gasp) ES5. I chose this approach for the following reasons.
Using Razor pages rather than a SPA aids in SEO and search engine ranking of public facing pages.
Loading Vue directly from a CDN eliminates a whole stack of Webpack centric technology from the learning curve which makes it much easier for new devs to get up to speed on the system.
The approach still provides the reactive goodness to UI development that Vue inherently brings to the table.
By keeping with the “page model” the code that delivers site functionality is logically grouped around the backend page that delivers that functionality.
Since Vue and Razor can do many of the same things, my goal for public facing pages is to use Razor to generate as close to the final html as possible, and to use Vue to add the reactiveness to the page. This delivers great SEO benefits for crawlers that index the page by parsing the HTML returned.
I realize the my usage of Vue is quite different than going the route of a SPA and WebPack and the approach often means I can't use 3rd party Vue Components without reworking the code a bit. But the approach simplifies the software architecture and delivers a lightweight reactive UI.
By using this approach Razor can be heavily leveraged to generate the initial rendering of the HTML with some tags containing vue attributes. Then after the page loads in the browser, Vue takes over and can reconfigure that page any way desired.
Obviously, this approach will not fit the needs of all developers or projects but for some use cases it's quite a nice setup.
A few more details for those interested
Since I use vue sitewide, my global _layout.aspx file is responsible for instantiating vue. Any sitewide functionality implemented in vue is implemented at this level. Many pages have page specific vue functionality, this is implemented as a mixin on that page or a mixin in a js file loaded by that page. When the _layout.aspx page instantiates Vue it does so with all the mixins that I have registered to a global mixin array. (The page pushed it's mixin on that global mixin array)
I don’t use .vue files. Any needed components are implemented either directly on the page or if they need to be used by multiple pages then they are implemented in a partial view like the one below.:
dlogViewComponent.cshtml :
#* dlog vue component template*#
<script type="text/x-template" id="dlogTemplate">
<div class="dlog" v-show="dlog.visible" v-on:click="dlog.closeBoxVisible ? close() : ''">
<div class="dlogCell">
<div class="dlogFrame" ##click.stop="" style="max-width:400px">
<i class="icon icon-close-thin-custom dlogCloseIcon" v-if="dlog.closeBoxVisible" ##click="close()"></i>
<div class="dlogCloseIconSpace" v-if="dlog.closeBoxVisible"></div>
<div class="dlogInner">
<div class="dlogTitle" style="float:left" v-text="title"></div>
<div class="clear"></div>
<div class="dlogContent">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</script>
#* Vue dlog component *#
<script type="text/javascript">
Vue.component('dlog', {
template: '#dlogTemplate',
props: { //don't mutate these!
closeBoxVisible: true,
title: 'One'
},
data: function () {
return {
dlog: { //nest the data props below dlog so I can use same names as cooresponding prop
closeBoxVisible: (typeof this.closeBoxVisible === 'undefined') ? true : (this.closeBoxVisible == 'true'),
title: (typeof this.title === 'undefined') ? '' : this.title,
visible: false
}
}
},
methods: {
//opens the dialog
open: function () {
app.hideBusy(); //just in case, no harm if not busy
this.dlog.visible = true;
var identifyingClass = this.getIdentifyingClass();
Vue.nextTick(function () {
$("." + identifyingClass).addClass("animateIn");
fx.manageDlogOnly();
});
},
//closes the dialog
close: function () {
fx.prepDlogClose();
var identifyingClass = this.getIdentifyingClass();
this.dlog.visible = false;
$("." + identifyingClass).removeClass("animateIn");
},
getIdentifyingClass: function () {
if (this.$el.classList.length > 1) {
//the last class is always our identifying css class.
return this.$el.classList[this.$el.classList.length - 1];
} else {
throw "A dialog must have an identifying class assigned to it.";
}
}
}
});
</script>
In the above, it's the Vue.component('dlog', ... part of the js that installs the component and makes it available to the page.
The vue code on the _layout.cshtml page looks something like the code below. By instantiating Vue on the _layout.cshtml which is used by the whole site, Vue is only instantiated in a single place sitewide:
_layout.cshtml :
<script type="text/javascript">
var app = new Vue({
el: '#appTemplate',
mixins: mixinArray, //The page adds it's mixin to mixinArray before this part of the layout executes.
data: {
errorMsg: '' //used sitewide for error messages
//other data used sitewide
},
methods: {
//methods that need to be available in vue sitewide, examples below:
showBusy: function (html) {
//functionality to show the user that the site is busy with an ajax request.
},
hideBusy: function () {
//functionality to hide the busy spinner and messaging
}
},
created: function () {
//this method is particularly useful for initializing data.
}
});
</script>
What I have provided here paints a pretty clear picture of this non-traditional approach and it's benefits. However, since several people asked, I also wrote a related blog post: Using VueJs with ASP.NET Razor Can Be Great!
You can do this. Sometimes you're obliged to do it, if, like us, you're migrating an existing code base and you can't convert everything at once. And as Ron C says, it works well.
If you're starting a new project, you have the luxury of choosing. Reasons for favouring an SPA and no Razor would be...
Reactivity. SPA apps generally feel (much) more reactive. Initial renders are often served from cache, before the data arrives. On first load, all resources arrive in a bundle, in one request-response. There's no, or much less, request chaining.
Workflow. Webpack, bundling and hot reloads are great. You get production builds, with minification, compilation of Vue render functions, elimination of 404 style errors, js syntax errors are trapped. The cycle from introducing an error to discovering it is greatly reduced for many errors.
SPA universe. Routing, Vuex, this really is the way of the future.
Purity. Razor and Vue do similar things at the end of the day. If you mix them, you may have a hard time keeping your head straight.
You can now also lint the VueJS templates within the Razor views:
https://www.npmjs.com/package/razor-vue-lint
Great answers and content on this question! Just to add re the OP, the official Vue documentation expressly states that you can mix and match and do what you like with Vue and that it is designed to be used incrementally so I'd say if it fits what you're trying to do then it is NOT an automatic bad practice.

Meteor: Proper way to add 'confirm delete' modal

I want to create a confirm delete popup with Bootstrap 3. Is there any good comprehensive examples how to build one. I am very new to Meteor.
Use whatever example from Codrops, etc, just remember put the JSCode inside a
Template.nameTemplate.rendered = function() {}
So thats telling meteor to load that jscode, when the template has beed rendered and it can load any modal, etc...
So just follow whatever example you want, and just put whatever jQuery plugin etc, inside Rendered function
Also in some case the rendered its not enough, you need to use too,you can see timer docs here, anyways if you are having bad time, feel free to upload, some meteorPad, free nitrous box o repo on github and i can help you (i have a bad time with those modals on meteor to, they are a little trickys =p)
update answer
try to add meteor add iron:router, and on the client /app.js
Router.route('/', function () {
this.render('leaderboard');
});
And keep the same rendered like this.
Template.deleteBtn.rendered = function(){
$('.open-modal').on('click', function(e){
$('#confirm').modal()
.on('click', '#delete', function (e) {
// Remove selected player
Players.remove(Session.get("selectedPlayer"));
});
});
}
UPDATE
So using the peppelg:bootstrap-3-modalPackage, you can easy do the follow
First Create a template with the modal content
<template name="modal">
<!-- Modal Stuff -->
</template>
and easy call it on a Event handler.
Template.example.events({
'click #exampleButton':function(){
Modal.show('modal')
}
})
Now back to this example check this meteorpad from line 1-23 on app.'s and 41-62 on main.html

Spacebars-generated dynamic links do not trigger Iron Router in Meteor?

I've got this in my routes:
Router.route('/videos/:id', {
name: 'VideoPage',
data: function(){
return Videos.findOne(this.params.id);
}
})
This template shows up at the route above:
Template.VideoPage.helpers({
'videoIds': function(){
var myVideoIds = [
"23456",
"h5e45t",
"f4e4w",
"g6h4h"
];
return myVideoIds;
}
});
HTML:
<template name="VideoPage">
{{videoTitle}}
<p>Click the links below to get to a new video page</p>
{{#each videoIds}}
<a href="/videos/" + {{this}}>
{{/each}}
</template>
When I click on a link, the URL in the browser changes from something like /videos/23456 to something like /videos/f4e4w, but the request never actually goes through Iron Router and the Router.route function. I put a debugger before Router.route and it triggers on initial page load but does NOT trigger when the links are clicked.
I understand that Iron Router's default behavior is NOT to re-render a template the user is currently on, but this is the same template with different url params that are used to change the data in the template, so IMO it should still re-render.
Ok, false alarm. It appears that Router.route DOES fire and updates the data context every time the params._id changes. I was placing the debugger outside of the Route function when I should have been placing it inside of the data context function.
The example that I gave in this answer was also a highy, highly simplified example. It wasn't working in my real-life project due to something else (a complex video iframe generator) that was being refreshed improperly.
But rest assured that going from /videos/f4e4w to /videos/23456 by clicking a link DOES still go through Iron Router and the params does get read and the data does get updated.

Need help pls: Meteor and Famous integration and creation of forms

I am currently using Meteor 0.9.2.2. I am trying to better understand how to build a form in Meteor + Famous without having to put each form element into a Famous surface.
I am using the "gadicohen:famous-views 0.1.7" and "mjnetworks:famous 0.2.2 "
I am using https://github.com/gadicc/meteor-famous-views and have looked at some of the samples of events. I can generate events on the view but seems to have lost the ability to generate events using Jquery (probably Famous alarm bells going off) for fields on the template.
(Note I fid try following What is a recommended way to get data into your meteor template from the front-end for a famous surface? but that just directed me to the examples I am following - sorry still stuck)
For example, if I wanted to have a "blur" event when a contenteditable field changed and used that to update the database, I am not sure how I do it.
BTW, I am bringing in the template via Iron-router:
this.route('someTemplate', {
path: '/',
});
Here's some sample of code of what I have been playing around with:
<template name="someTemplate">
{{#Scrollview target="content" size="[undefined,100]"}}
{{#Surface class="green-bg"}}
<h4 id="main-edit-title" class="editable" data-fieldname="title" data-resourceid="{{_id}}" contenteditable=true>Heading</h4>
<p id="main-edit-message" class="mediumEditable editable" data-fieldname="message" data-resourceid="{{_id}}" contenteditable=true>Summary</p>
{{/Surface}}
{{/Scrollview}}
</template>
Template.someTemplate.events({
'blur .editable': function (e) {
e.preventDefault();
//e.stopPropagation();
var item = $(e.currentTarget);
DO SOME UPDATE STUFF TO THE DATABASE
item.removeClass('resourceUpdated');
},
});
I looked at the 'famousEvents' too and could not seem to get that working. Ie no events fired and that would only be at the surface level, not the field level.
At the view level I was fine and code below worked fine:
Template.someTemplate.rendered = function() {
var fview = FView.from(this);
var target = fview.surface || fview.view._eventInput;
target.on('click', function() {
clickeyStuff(fview);
});
}
I tried the other variants from this page: https://famous-views.meteor.com/examples/events
So the core questions, I think, is: Do I have to move every form element to a Famous Surface? This would be a killer. I am hoping I can still use Jquery or access the DOM for stuff within the template. Note I do see stuff in the Famous FAQ http://famo.us/guides/pitfalls that says don't touch the DOM... so happy to find out how else I should be doing this???
I tried to make this clearer on the events example page, but I guess I'm still not there yet. I'll answer below but please feel free to chime in with how I can improve the documentation.
Inside of a Surface is basically regular Meteor. But outside of a Surface is the realm of famous-views. So you need to have a Meteor template inside of a Surface for events to attach themselves properly - and, as noted in the docs - that template needs to have at least one element in side of it to attach the events. So either (and in both cases, renaming the outer template wrapper but leaving Template.someTemplate.events as is):
<template name="someTemplateWrapper">
{{#Scrollview target="content" size="[undefined,100]"}}
{{#Surface class="green-bg"}}
{{> someTemplate}}
{{/Surface}}
{{/Scrollview}}
</template>
or simply:
<template name="someTemplateWrapper">
{{#Scrollview target="content" size="[undefined,100]"}}
{{>Surface template="someTemplate" class="green-bg"}}
{{/Scrollview}}
</template>
and then move all the Meteor stuff that needs events to it's own template where the events are handled:
<template name="someTemplate">
<h4 id="main-edit-title" class="editable" data-fieldname="title" data-resourceid="{{_id}}" contenteditable=true>Heading</h4>
<p id="main-edit-message" class="mediumEditable editable" data-fieldname="message" data-resourceid="{{_id}}" contenteditable=true>Summary</p>
</template>
Hope that makes sense, just rushing out... let me know if anything is not clear and I'll ammend the answer later.

Callback after the DOM was updated in Meteor.js

I have this Meteor project: https://github.com/jfahrenkrug/code_buddy
It's basically a tool with a big textarea and a pre area that lets you enter source code snippets that automatically get pushed to all connected clients.
I'd like to automatically run the highlightSyntax function when the code was changed, but it doesn't really work.
I've tried query.observe, but that didn't work too well: The syntax highlight flashed up once and then disappeared again.
So my question is: How do I run code after the DOM was updated?
A hacky way to do it is:
foo.html
<template name="mytemplate">
<div id="my-magic-div">
.. stuff goes here ..
{{add_my_special_behavior}}
</div>
</template>
foo.js
Template.mytemplate.add_my_special_behavior = function () {
Meteor.defer(function () {
// find #my-magic-div in the DOM
// do stuff to it
});
// return nothing
};
The function will get called whenever the template is rendered (or re-rendered), so you can use it as a hook to do whatever special DOM manipulation you want to do. You need to use Meteor.defer (which does the same thing as settimeout(f, 0)) because at the time the template is being rendered, it isn't in the DOM yet.
Keep in mind that you can render a template without inserting it in the DOM! For example, it's totally legal to do this:
console.log(Template.mytemplate())
So when a template is rendered, there is not a 100% guarantee that it is going to end up in the DOM. It's up to the user of the template.
Starting with Meteor 0.4.0, Template.myTemplate.rendered provides a callback that
is called once when an instance of Template.myTemplate is rendered into DOM nodes and put into the document for the first time.
More info at http://docs.meteor.com/#template_rendered
As for the current version of Meteor (1.0), we can now use the .afterFlush() function of Tracker.
Tracker.autorun(function(e){
var data = Router.current().data();
if(data.key !== undefined){
//the data is there but dom may not be created yet
Tracker.afterFlush(function(){
//dom is now created.
});
}
});
There is no callback after the DOM is updated, however you can force all pending DOM updates with Tracker.flush().
After you call flush(), you know the DOM has been updated and so you can perform any manual DOM changes you need.
This question is quite old, but the two-year-later solution would be to integrate an operational transformation library with Meteor and use Ace or CodeMirror on the client, which does the syntax highlighting automatically. This has the additional benefit of allowing people to edit at the same time.
I've already done the work for you :)
In Blaze Components (I am one of authors) you have an API which calls methods when DOM is inserted, moved, or removed. You can see here how to make an reactive variable when DOM changes.
The downside with this approach is that it does not change when DOM element attributes change (like class change). Only when DOM elements themselves are changed. This works for most cases, but if you need the second, I suggest you simply use MutationObserver. In this case you will be able to respond also to outside changes.
It seems Template.myTemplate.rendered doesn't work properly or I don't get it...
I need to load TinyMCE inline after a template with all posts are rendered, so I have :
- a Template
<div id="wrapper">
{{#each posts}}
<div class="editable">{{post}}</div>
{{/each}}
</div>
- and a Function
Template.myPosts.rendered = function(){
console.dir($("div"));
tinymce.init({
selector: "div.editable",
inline: true,
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table contextmenu paste"
],
toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
});
}
However the console logs only the <div id="wrapper"> and not the <div class="editable"> divs, which contain my posts. So, it seems Template.myTemplate.rendered callback occures before the template is rendered, right?
EDIT: I put the Template.myTemplate.rendered code inside a setTimeout() and all seems to work, so I'm sure Template.myTemplate.rendered causes the problem.
I just found a little hack that seems to be working pretty well:
Template.myTemplate.onRendered(function() {
this.autorun(function() {
Meteor.setTimeout(function() {
// DOM has been updated
}, 1);
});
});
I'm not a Meteor expert so it might have some downsides, but I haven't found any for now — except that it's a bit dirty !
I think you might want to pass a callback to
Meteor.startup(callback)
see http://docs.meteor.com/#meteor_startup

Resources