I am creating a slideshow using AngularJS and Reveal.js. Reveal require the form
<div class="slides">
<section>
</section>
</div>
for horizontal slides. While vertical slides have two sections:
<div class="slides">
<section>
<section>
</section>
</section>
</div>
I am rendering this page using angular:
<div ng-app='myApp' class="reveal">
<div class="slides" ng-controller='MyController'>
<section slide ng-repeat="slide in slides">
</section>
</div>
</div>
<script src="https://raw.github.com/hakimel/reveal.js/master/js/reveal.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.js"></script>
</body>
</html>
Slides with mutiple steps should be vertical. All others should be horizontal. The controller returns steps and setting up Reveal:
function MyController($scope) {
$scope.slides = [
{ 'steps': ['a'] },
{ 'steps': ['b1', 'b2'] },
{ 'steps': ['c1'] }
];
setTimeout(function() {
Reveal.initialize({
loop: false,
transition: Reveal.getQueryHash().transition || 'none'
});
}, .1 * 1000);
}
The directive needs to add a new element and attribute around the step. Here is my ugly, embarrassing, imperative, jQuery like directive:
app.directive('slide', function () {
var wrapContent = function (content) {
return '<h1>' + content + '</h1>';
};
return {
restrict: 'A'
,link: function (scope, element, attrs, controller) {
// resorting to imparative jQuery way
if (scope.slide.steps.length == 1) {
element.html(
wrapContent(scope.slide.steps[0])
);
} else {
var sections = '';
for (i=0,len=scope.slide.steps.length; i < len; ++i) {
sections +=
'<section ' +
function () {
result = '';
if (i !== len-1) {
result = 'data-autoslide="1000" ';
}
return result;
}() +
'>' +
wrapContent(scope.slide.steps[i]) +
'</section>';
}
element.html(sections);
}
}
}
});
How do I write this such that it looks like angular code? jsfiddle
I have tried compile, link, replace, ng-switch all to no avail.
Since you have all sections defined in your slides property on the scope, I would probably move the entire slideshow to a directive.
<div ng-app='myApp' class='reveal' ng-controller='MyController'>
<div slideshow='slides'></div>
</div>
Inside the directive itself, I would iterate over each of the slides and create a <section> element. For each slide, iterate over the steps and create a <section> element (if there's more than one) or an <h1> element (if there's only one). It might look something like this:
for (var i = 0; i < slides.length; i++) {
var section = angular.element("<section>");
var steps = scope.slides[i].steps;
if (steps.length == 1) {
var content = angular.element("<h1>").html(steps[0]);
section.append(content);
} else {
for (var j = 0; j < steps.length; j++) {
var subSection = angular.element("<section>");
if (j < steps.length - 1)
subSection.attr('data-autoslide', '1000');
var content = angular.element("<h1>").html(steps[j]);
subSection.append(content);
section.append(subSection);
}
}
}
You could then append section to the directive's element. And since, inside the directive, we know that the DOM is done being built, we could move the call to Reveal.initialize from the controller (where it's usually not the greatest idea to do DOM manipulation or library calls) into the directive itself.
Put it all together with a couple minor changes, and you might end up with the code at this jsFiddle: http://jsfiddle.net/BinaryMuse/CXqAb/
While you could probably write this code in a more declarative style in the directive, I think this is probably a little more readable, and when integrating with third party libraries like Reveal that expect a certain DOM structure, results in an easier time creating a predictable and clean DOM structure than you might achieve with a bunch of DOM elements with ngShows and ngSwitches scattered among them.